appsec-agent 2.7.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 (83) hide show
  1. package/README.md +57 -18
  2. package/conf/appsec_agent.yaml +13 -0
  3. package/dist/bin/agent-run.js +16 -18
  4. package/dist/bin/agent-run.js.map +1 -1
  5. package/dist/conf/appsec_agent.yaml +13 -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 +22 -2
  11. package/dist/src/agent_actions.d.ts.map +1 -1
  12. package/dist/src/agent_actions.js +144 -26
  13. package/dist/src/agent_actions.js.map +1 -1
  14. package/dist/src/agent_options.d.ts +43 -83
  15. package/dist/src/agent_options.d.ts.map +1 -1
  16. package/dist/src/agent_options.js +237 -280
  17. package/dist/src/agent_options.js.map +1 -1
  18. package/dist/src/index.d.ts +1 -0
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/index.js +7 -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 +75 -7
  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/fp_adversary_pass.d.ts +188 -0
  74. package/dist/src/schemas/fp_adversary_pass.d.ts.map +1 -0
  75. package/dist/src/schemas/fp_adversary_pass.js +258 -0
  76. package/dist/src/schemas/fp_adversary_pass.js.map +1 -0
  77. package/dist/src/utils.js +1 -1
  78. package/dist/src/utils.js.map +1 -1
  79. package/package.json +4 -4
  80. package/dist/src/openai_tools.d.ts +0 -26
  81. package/dist/src/openai_tools.d.ts.map +0 -1
  82. package/dist/src/openai_tools.js +0 -194
  83. 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");
@@ -15,6 +14,13 @@ const qa_context_1 = require("./schemas/qa_context");
15
14
  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");
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; } });
18
24
  const FIX_CODE_VS_OPTIONS_GUIDANCE = `
19
25
 
20
26
  FIXED CODE vs FIX OPTIONS:
@@ -24,65 +30,23 @@ FIXED CODE vs FIX OPTIONS:
24
30
  or when multiple valid remediation approaches exist. Each option needs an id, title, and description.
25
31
  - Provide either fixed_code OR fix_options per finding, not both.`;
26
32
  /**
27
- * Default identifier the agent uses to register a parent-app-managed MCP
28
- * server with the Claude Agent SDK. The resulting tool names exposed to
29
- * the LLM follow the SDK convention `mcp__<server>__<tool>`, so this
30
- * value becomes the literal prefix the model sees on its tool list.
33
+ * System-prompt suffix for `pr_reviewer` / `code_reviewer` when
34
+ * `--mcp-server-url` is set. Steers the model toward all live parent-app
35
+ * MCP tools by exact SDK tool id — `queryFindingsHistory`,
36
+ * `queryImportGraph`, `queryRuntimeEnrichment`, and `queryCodebaseGraph`
37
+ * (parent-app plan §8.18 Phase 3: bounded structural graph queries; no raw
38
+ * Cypher).
31
39
  *
32
- * The default is intentionally generic (`appsec-internal`) so the
33
- * `appsec-agent` package is reusable across parent apps. Callers that
34
- * need a different identifier e.g. to keep an existing prompt-nudge or
35
- * counter contract stable pass `--mcp-server-name <name>` on the CLI
36
- * (or `mcpServerName` to the role builders directly), and the override
37
- * threads through to both `Options.mcpServers[<name>]` and the namespaced
38
- * tool names in the subagent whitelist.
39
- */
40
- exports.DEFAULT_MCP_SERVER_NAME = 'appsec-internal';
41
- /**
42
- * @deprecated since v2.4.2 — historical alias for the default server name.
43
- * Kept exported so existing imports keep type-checking; new code should
44
- * read `DEFAULT_MCP_SERVER_NAME` (and pass an override via
45
- * `mcpServerName` when a specific identifier is required).
46
- */
47
- exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME;
48
- /**
49
- * Tools exposed by the per-scan in-process MCP server the parent app
50
- * stands up. Pinned at this set (`queryFindingsHistory`,
51
- * `queryImportGraph`, `queryRuntimeEnrichment`, `queryCodebaseGraph`) so
52
- * the agent's tool whitelist is deterministic at the current version;
53
- * parent apps that expose a different surface should fork or extend this
54
- * list rather than rely on dynamic discovery (the SDK would otherwise have
55
- * to round-trip the server before constructing the whitelist).
56
- */
57
- exports.MCP_INTERNAL_TOOL_NAMES = [
58
- 'queryFindingsHistory',
59
- 'queryImportGraph',
60
- 'queryRuntimeEnrichment',
61
- 'queryCodebaseGraph',
62
- ];
63
- /**
64
- * SDK-namespaced tool names the LLM sees on its tool list when the server
65
- * is wired up. Exposed as a helper rather than a constant so callers in
66
- * `agent_options.ts` and the test suite share one source of truth.
67
- *
68
- * @param serverName - Override for the MCP server identifier. Defaults
69
- * to `DEFAULT_MCP_SERVER_NAME` (`appsec-internal`).
70
- */
71
- function buildMcpInternalToolNames(serverName = exports.DEFAULT_MCP_SERVER_NAME) {
72
- return exports.MCP_INTERNAL_TOOL_NAMES.map((tool) => `mcp__${serverName}__${tool}`);
73
- }
74
- /**
75
- * System-prompt suffix for `pr_reviewer` when `--mcp-server-url` is set.
76
- * Steers the model toward all live parent-app MCP tools by exact SDK tool id
77
- * — `queryFindingsHistory`, `queryImportGraph`, `queryRuntimeEnrichment`, and
78
- * `queryCodebaseGraph` (parent-app plan §8.18 Phase 3: bounded structural graph
79
- * queries; no raw Cypher).
40
+ * v2.8.0: extended to `code_reviewer` (full-repo, Lane 2). Previously only
41
+ * `pr_reviewer` received the nudge; `code_reviewer` had MCP attached only
42
+ * through `getDiffReviewerOptions` when called with `--diff-context`, never
43
+ * through `getCodeReviewerOptions`. Closing that gap is the B5a deliverable.
80
44
  *
81
45
  * @param mcpServerName - Same override as `attachMcpServerToOptions`
82
46
  * (`DEFAULT_MCP_SERVER_NAME` when omitted).
83
47
  */
84
- function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = exports.DEFAULT_MCP_SERVER_NAME) {
85
- 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;
86
50
  const findingsTool = `mcp__${name}__queryFindingsHistory`;
87
51
  const importGraphTool = `mcp__${name}__queryImportGraph`;
88
52
  const runtimeTool = `mcp__${name}__queryRuntimeEnrichment`;
@@ -94,24 +58,13 @@ function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = exports.DEFAU
94
58
  }
95
59
  /**
96
60
  * Mutate an already-built `Options` object to attach the MCP server config
97
- * (top-level `mcpServers`) and extend the named subagent's `tools`
98
- * whitelist with the backend-backed tool surface. No-op when
99
- * `mcpServerUrl` is falsy — preserves the pre-v2.4.0 behaviour for
100
- * callers that don't pass the new parameter.
101
- *
102
- * Mutating in place (rather than returning a fresh object) keeps the
103
- * existing role builders' return statements untouched and avoids a deep
104
- * clone of the (large) `Options` shape.
105
- *
106
- * @param mcpServerName - Override for the server identifier. Defaults
107
- * to `DEFAULT_MCP_SERVER_NAME` (`appsec-internal`); pass the parent
108
- * app's chosen name to keep tool-name contracts stable.
61
+ * @deprecated Use attachMcpToRoleSpec + roleSpecToClaudeOptions instead.
109
62
  */
110
- 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) {
111
64
  if (!mcpServerUrl) {
112
65
  return;
113
66
  }
114
- const serverName = mcpServerName || exports.DEFAULT_MCP_SERVER_NAME;
67
+ const serverName = mcpServerName || mcp_internal_1.DEFAULT_MCP_SERVER_NAME;
115
68
  const httpEntry = {
116
69
  type: 'http',
117
70
  url: mcpServerUrl,
@@ -126,7 +79,7 @@ function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName
126
79
  const agent = options.agents?.[agentKey];
127
80
  if (agent) {
128
81
  const existingTools = agent.tools ?? [];
129
- agent.tools = [...existingTools, ...buildMcpInternalToolNames(serverName)];
82
+ agent.tools = [...existingTools, ...(0, mcp_internal_1.buildMcpInternalToolNames)(serverName)];
130
83
  }
131
84
  }
132
85
  class AgentOptions {
@@ -174,83 +127,109 @@ class AgentOptions {
174
127
  /**
175
128
  * Get options for simple query agent
176
129
  */
177
- getSimpleQueryAgentOptions(role = 'simple_query_agent', srcDir) {
130
+ getSimpleQueryAgentRoleSpec(role = 'simple_query_agent', srcDir) {
178
131
  const roleConfig = this.confDict[this.environment]?.[role];
179
132
  let systemPrompt = roleConfig?.options?.system_prompt ||
180
133
  'You are an Application Security (AppSec) expert assistant. You are responsible for providing security advice and guidance to the user.';
181
- // Add source directory context to system prompt if provided
182
134
  if (srcDir) {
183
135
  systemPrompt += ` You have access to a source code directory at ${srcDir} that you can search and read files from to answer questions.`;
184
136
  }
185
137
  return {
186
- systemPrompt: systemPrompt,
187
- 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,
188
145
  };
189
146
  }
147
+ getSimpleQueryAgentOptions(role = 'simple_query_agent', srcDir) {
148
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getSimpleQueryAgentRoleSpec(role, srcDir));
149
+ }
190
150
  /**
191
151
  * Get options for security code reviewer
152
+ *
153
+ * v2.8.0 (B5a fix): now accepts MCP server params and calls
154
+ * `attachMcpServerToOptions` to wire the parent-app per-scan MCP server.
155
+ * Previously the backend pushed `--mcp-server-name` to the spawn but the
156
+ * SDK was never told MCP tools existed — the full-repo code_reviewer ran
157
+ * without `queryFindingsHistory` / `queryImportGraph` /
158
+ * `queryCodebaseGraph` / `queryRuntimeEnrichment` access despite the
159
+ * parent app starting the server. Mirror's `getDiffReviewerOptions`
160
+ * pattern and includes the same system-prompt nudge as `pr_reviewer` so
161
+ * the agent discovers the tools.
162
+ *
192
163
  * @param role - The role configuration key
193
164
  * @param outputFormat - Output format (json, markdown, etc.)
165
+ * @param mcpServerUrl - Parent-app per-scan MCP server URL
166
+ * @param mcpServerName - Override for the MCP server identifier
167
+ * @param mcpServerBearer - Bearer token for MCP HTTP requests
194
168
  */
195
- getCodeReviewerOptions(role = 'code_reviewer', outputFormat) {
169
+ getCodeReviewerRoleSpec(role = 'code_reviewer', outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer, workingDirectory) {
196
170
  const roleConfig = this.confDict[this.environment]?.[role];
197
171
  let systemPrompt = roleConfig?.options?.system_prompt ||
198
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.';
199
173
  if (outputFormat?.toLowerCase() === 'json') {
200
174
  systemPrompt += FIX_CODE_VS_OPTIONS_GUIDANCE;
201
175
  }
202
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 30;
203
- const options = {
204
- agents: {
205
- 'code-reviewer': {
206
- description: 'Reviews code for best practices and potential security issues only',
207
- prompt: systemPrompt,
208
- tools: ['Read', 'Grep', 'Write'],
209
- model: this.model,
210
- maxTurns: resolvedMaxTurns
211
- }
212
- },
213
- permissionMode: 'bypassPermissions'
176
+ if (mcpServerUrl) {
177
+ systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
178
+ }
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,
214
189
  };
215
- // Add JSON schema enforcement when output format is JSON
216
190
  if (outputFormat?.toLowerCase() === 'json') {
217
- options.outputFormat = {
218
- type: 'json_schema',
219
- schema: security_report_1.SECURITY_REPORT_SCHEMA
220
- };
191
+ spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
221
192
  }
222
- 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));
223
198
  }
224
199
  /**
225
- * Get options for threat modeler
226
- * @param role - The role configuration key
227
- * @param outputFormat - Output format (json, markdown, etc.)
200
+ * Provider-neutral spec for threat modeler (Phase 3 RoleSpec spike).
228
201
  */
229
- getThreatModelerOptions(role = 'threat_modeler', outputFormat) {
202
+ getThreatModelerRoleSpec(role = 'threat_modeler', outputFormat, workingDirectory) {
230
203
  const roleConfig = this.confDict[this.environment]?.[role];
231
204
  const systemPrompt = roleConfig?.options?.system_prompt ||
232
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.';
233
206
  const isJson = outputFormat?.toLowerCase() === 'json';
234
207
  const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 20;
235
- const options = {
236
- agents: {
237
- 'threat-modeler': {
238
- description: 'Performs threat modeling and risk assessment using STRIDE methodology',
239
- prompt: systemPrompt,
240
- tools: isJson ? ['Read', 'Grep'] : ['Read', 'Grep', 'Write', 'Graphviz'],
241
- model: this.model,
242
- maxTurns: resolvedMaxTurns
243
- }
244
- },
245
- 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,
246
220
  };
247
221
  if (isJson) {
248
- options.outputFormat = {
249
- type: 'json_schema',
250
- schema: threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA
251
- };
222
+ spec.outputSchema = threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA;
252
223
  }
253
- 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));
254
233
  }
255
234
  /**
256
235
  * Get options for PR diff-focused code reviewer
@@ -260,7 +239,7 @@ class AgentOptions {
260
239
  * @param srcDir - Optional source directory path
261
240
  * @param outputFormat - Output format (json, markdown, etc.)
262
241
  */
263
- 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) {
264
243
  const roleConfig = this.confDict[this.environment]?.[role];
265
244
  let systemPrompt;
266
245
  if (noTools) {
@@ -316,7 +295,6 @@ You have access to Read, Grep, and Write tools:
316
295
  if (srcDir) {
317
296
  systemPrompt += `\n\nSource directory available at: ${srcDir}`;
318
297
  }
319
- // Allow role config to override the system prompt
320
298
  if (roleConfig?.options?.diff_reviewer_system_prompt) {
321
299
  systemPrompt = roleConfig.options.diff_reviewer_system_prompt;
322
300
  }
@@ -331,76 +309,59 @@ You have access to Read, Grep, and Write tools:
331
309
  if (mcpServerUrl && role === 'pr_reviewer') {
332
310
  systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
333
311
  }
334
- const resolvedMaxTurns = maxTurns
335
- ?? roleConfig?.options?.max_turns
336
- ?? 10;
337
- const options = {
338
- agents: {
339
- 'diff-reviewer': {
340
- description: 'Reviews PR diff changes for security vulnerabilities',
341
- prompt: systemPrompt,
342
- tools: noTools ? ['Write'] : ['Read', 'Grep', 'Write'],
343
- model: this.model,
344
- maxTurns: resolvedMaxTurns
345
- }
346
- },
347
- 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,
348
323
  };
349
- // Add JSON schema enforcement when output format is JSON
350
324
  if (outputFormat?.toLowerCase() === 'json') {
351
- options.outputFormat = {
352
- type: 'json_schema',
353
- schema: security_report_1.SECURITY_REPORT_SCHEMA
354
- };
325
+ spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
355
326
  }
356
- attachMcpServerToOptions(options, mcpServerUrl, 'diff-reviewer', mcpServerName, mcpServerBearer);
357
- return options;
327
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
328
+ return spec;
358
329
  }
359
- /**
360
- * Get options for code fixer agent
361
- * Uses structured JSON output to guarantee a well-formed fix response.
362
- * Has Read and Grep tools to explore the source directory for additional context.
363
- * @param role - The role configuration key
364
- * @param srcDir - Optional source directory path for additional context
365
- */
366
- 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) {
367
334
  const roleConfig = this.confDict[this.environment]?.[role];
368
335
  let systemPrompt = roleConfig?.options?.system_prompt ||
369
336
  'You are an expert security engineer specializing in fixing vulnerabilities in code. ' +
370
337
  'You receive a finding with code context and must produce a precise, minimal fix that resolves ' +
371
- '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. " +
372
339
  'Only modify the affected lines. Always use the recommended secure alternatives when applicable.';
373
340
  if (srcDir) {
374
341
  systemPrompt += `\n\nSource directory available at: ${srcDir}. You may read files for additional context if needed.`;
375
342
  }
376
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 10;
377
- const options = {
378
- agents: {
379
- 'code-fixer': {
380
- description: 'Generates precise security fixes for code vulnerabilities',
381
- prompt: systemPrompt,
382
- tools: ['Read', 'Grep'],
383
- model: this.model,
384
- maxTurns: resolvedMaxTurns
385
- }
386
- },
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 },
387
350
  permissionMode: 'bypassPermissions',
388
- outputFormat: {
389
- type: 'json_schema',
390
- schema: security_fix_1.FIX_OUTPUT_SCHEMA
391
- }
351
+ model: this.model,
352
+ outputSchema: security_fix_1.FIX_OUTPUT_SCHEMA,
353
+ workingDirectory: srcDir ?? undefined,
392
354
  };
393
- attachMcpServerToOptions(options, mcpServerUrl, 'code-fixer', mcpServerName, mcpServerBearer);
394
- return options;
355
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
356
+ return spec;
395
357
  }
396
- /**
397
- * Get options for the QA verifier agent
398
- * Uses Read, Grep, and Bash tools for test execution and analysis
399
- */
400
- 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) {
401
362
  const roleConfig = this.confDict[this.environment]?.[role];
402
363
  let systemPrompt = roleConfig?.options?.system_prompt ||
403
- '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 " +
404
365
  'and analyzing the results. You have access to the project source code and can execute shell commands to run tests. ' +
405
366
  'First, set up the environment (install dependencies if needed), then run the test suite. ' +
406
367
  'If tests fail, analyze the failures to determine if they are caused by the security fix or are pre-existing issues. ' +
@@ -408,30 +369,23 @@ You have access to Read, Grep, and Write tools:
408
369
  if (srcDir) {
409
370
  systemPrompt += `\n\nProject source code is available at: ${srcDir}. Use Read and Grep to inspect files, and Bash to execute commands.`;
410
371
  }
411
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 15;
412
- const options = {
413
- agents: {
414
- 'qa-verifier': {
415
- description: 'Verifies security fixes by running project tests and analyzing results',
416
- prompt: systemPrompt,
417
- tools: ['Read', 'Grep', 'Bash'],
418
- model: this.model,
419
- maxTurns: resolvedMaxTurns
420
- }
421
- },
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 },
422
379
  permissionMode: 'bypassPermissions',
423
- outputFormat: {
424
- type: 'json_schema',
425
- schema: qa_context_1.QA_VERDICT_SCHEMA
426
- }
380
+ model: this.model,
381
+ outputSchema: qa_context_1.QA_VERDICT_SCHEMA,
382
+ workingDirectory: srcDir ?? undefined,
427
383
  };
428
- return options;
429
384
  }
430
- /**
431
- * Get options for the finding validator agent
432
- * Uses Read and Grep tools (read-only) to analyze code for vulnerability presence.
433
- */
434
- 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') {
435
389
  const roleConfig = this.confDict[this.environment]?.[role];
436
390
  const systemPrompt = roleConfig?.options?.system_prompt ||
437
391
  'You are a security-aware software analyst. Your task is to analyze repository files and metadata ' +
@@ -445,25 +399,23 @@ You have access to Read, Grep, and Write tools:
445
399
  '(logs, uploads, work-dir, data), IDE config (.cursor, .vscode), utility scripts, and documentation. Use ' +
446
400
  'specific paths from the tree (e.g., "backend/scripts/**" not just "scripts/**"). Only suggest patterns NOT ' +
447
401
  'already in the standard preset. If a field has no relevant information, return an empty string.';
448
- const options = {
449
- agents: {
450
- 'context-extractor': {
451
- description: 'Extracts structured project intelligence from repository files',
452
- prompt: systemPrompt,
453
- tools: [],
454
- model: this.model,
455
- maxTurns: 1,
456
- },
457
- },
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: [],
458
410
  permissionMode: 'bypassPermissions',
459
- outputFormat: {
460
- type: 'json_schema',
461
- schema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
462
- },
411
+ model: this.model,
412
+ outputSchema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
463
413
  };
464
- return options;
465
414
  }
466
- 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) {
467
419
  const roleConfig = this.confDict[this.environment]?.[role];
468
420
  let systemPrompt = roleConfig?.options?.system_prompt ||
469
421
  'You are a security expert specializing in vulnerability validation. ' +
@@ -473,45 +425,25 @@ You have access to Read, Grep, and Write tools:
473
425
  if (srcDir) {
474
426
  systemPrompt += `\n\nSource code is available at: ${srcDir}. Use Read and Grep to inspect files for additional context if needed.`;
475
427
  }
476
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 5;
477
- const options = {
478
- agents: {
479
- 'finding-validator': {
480
- description: 'Validates whether a previously detected security vulnerability is still present in code',
481
- prompt: systemPrompt,
482
- tools: ['Read', 'Grep'],
483
- model: this.model,
484
- maxTurns: resolvedMaxTurns
485
- }
486
- },
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 },
487
435
  permissionMode: 'bypassPermissions',
488
- outputFormat: {
489
- type: 'json_schema',
490
- schema: finding_validator_1.RETEST_VERDICT_SCHEMA
491
- }
436
+ model: this.model,
437
+ outputSchema: finding_validator_1.RETEST_VERDICT_SCHEMA,
438
+ workingDirectory: srcDir ?? undefined,
492
439
  };
493
- attachMcpServerToOptions(options, mcpServerUrl, 'finding-validator', mcpServerName, mcpServerBearer);
494
- return options;
440
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
441
+ return spec;
495
442
  }
496
- /**
497
- * learned_guidance_synthesizer (v2.5.0 / parent-app plan §3.8): pure-transform
498
- * role that condenses bucketed dismissal/outcome/feedback signals into a short
499
- * list of class-level policy bullets the pr_reviewer reads next scan.
500
- *
501
- * Tools: NONE — the agent must work from the provided buckets only. No
502
- * source-tree access, no MCP server. The parent app's `runSynthesizerAgent`
503
- * spawns this from a temp working directory anyway, so even if a tool were
504
- * attached it would have nothing to read.
505
- *
506
- * Output schema (LEARNED_GUIDANCE_OUTPUT_SCHEMA):
507
- * { bullets: [{ cwe, bullet (≤300 chars), confidence (0..1) }] }
508
- *
509
- * Confidence floor and active-bullet cap are enforced by the parent app
510
- * (`MIN_CONFIDENCE = 0.6`, `MAX_ACTIVE_BULLETS_PER_PROJECT = 12`); this
511
- * role is allowed to return up to 50 bullets and the parent ranks +
512
- * truncates.
513
- */
514
- 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') {
515
447
  const roleConfig = this.confDict[this.environment]?.[role];
516
448
  const systemPrompt = roleConfig?.options?.system_prompt ||
517
449
  'You are a senior application security engineer summarizing patterns from past PR-scan ' +
@@ -523,30 +455,23 @@ You have access to Read, Grep, and Write tools:
523
455
  'vague to ground a specific rule, OMIT that bucket entirely — it is better to return zero ' +
524
456
  'bullets than a bullet the reviewer cannot act on. Output is constrained to the required ' +
525
457
  'JSON schema; emit nothing else.';
526
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 1;
527
- const options = {
528
- agents: {
529
- 'learned-guidance-synthesizer': {
530
- description: 'Synthesizes class-level learned-guidance bullets from per-CWE dismissal-signal buckets',
531
- prompt: systemPrompt,
532
- tools: [],
533
- model: this.model,
534
- maxTurns: resolvedMaxTurns,
535
- },
536
- },
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: [],
537
466
  permissionMode: 'bypassPermissions',
538
- outputFormat: {
539
- type: 'json_schema',
540
- schema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
541
- },
467
+ model: this.model,
468
+ outputSchema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
542
469
  };
543
- return options;
544
470
  }
545
- /**
546
- * pr_adversary: second pass that filters pr_reviewer findings using failure-path skepticism.
547
- * Output: same SECURITY_REPORT_SCHEMA with only surviving findings.
548
- */
549
- 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) {
550
475
  const roleConfig = this.confDict[this.environment]?.[role];
551
476
  let systemPrompt = roleConfig?.options?.system_prompt ||
552
477
  'You are a senior application security engineer performing an adversarial second pass on security findings. ' +
@@ -560,25 +485,57 @@ You have access to Read, Grep, and Write tools:
560
485
  systemPrompt +=
561
486
  '\n\n**Experiment (treatment):** Bias toward dropping borderline issues unless the diff plus quick repo checks show a real attack surface.';
562
487
  }
563
- const resolvedMaxTurns = maxTurns ?? roleConfig?.options?.max_turns ?? 15;
564
- const options = {
565
- agents: {
566
- 'pr-adversary': {
567
- description: 'Adversarial second pass: filters PR scan findings by concrete failure paths',
568
- prompt: systemPrompt,
569
- tools: ['Read', 'Grep'],
570
- model: this.model,
571
- maxTurns: resolvedMaxTurns,
572
- },
573
- },
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 },
495
+ permissionMode: 'bypassPermissions',
496
+ model: this.model,
497
+ outputSchema: security_report_1.SECURITY_REPORT_SCHEMA,
498
+ workingDirectory: srcDir ?? undefined,
499
+ };
500
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
501
+ return spec;
502
+ }
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) {
507
+ const roleConfig = this.confDict[this.environment]?.[role];
508
+ let systemPrompt = roleConfig?.options?.system_prompt ||
509
+ 'You are a senior application security engineer performing an adversarial false-positive review on a full-repository security scan. ' +
510
+ 'For each candidate finding, return a verdict (confirm or dismiss) with a numeric 0.0-1.0 confidence and a concrete rationale. ' +
511
+ 'Weight the supplied project posture (security context, deployment context, developer guidance) when assessing each finding. ' +
512
+ 'Use Read/Grep and any available MCP tools to verify reachability before confirming. ' +
513
+ 'Dismiss only when you can name the specific mitigation, the reachability gap, or the test-only nature of the code.';
514
+ if (srcDir) {
515
+ systemPrompt += `\n\nSource code is available at: ${srcDir}. Use Read and Grep to verify call paths and mitigations before issuing a verdict.`;
516
+ }
517
+ systemPrompt +=
518
+ '\n\nReturn one JSON object matching the `fp_adversary_report` schema. Each verdict must echo the input `fingerprint` so the parent app can route the verdict to the right finding. Missing verdicts are treated as `confirm` (no silent drops).';
519
+ if (mcpServerUrl) {
520
+ systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
521
+ }
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 },
574
529
  permissionMode: 'bypassPermissions',
575
- outputFormat: {
576
- type: 'json_schema',
577
- schema: security_report_1.SECURITY_REPORT_SCHEMA,
578
- },
530
+ model: this.model,
531
+ outputSchema: fp_adversary_pass_1.FP_ADVERSARY_REPORT_SCHEMA,
532
+ workingDirectory: srcDir ?? undefined,
579
533
  };
580
- attachMcpServerToOptions(options, mcpServerUrl, 'pr-adversary', mcpServerName, mcpServerBearer);
581
- 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));
582
539
  }
583
540
  }
584
541
  exports.AgentOptions = AgentOptions;