@bbradar/mcp 0.1.12 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -20,6 +20,7 @@ const MAX_FIND_TARGET_SAMPLE_PROGRAMS = 10;
20
20
  const MAX_FIND_TARGET_SAMPLES = 20;
21
21
  const MAX_FIND_UPSTREAM_REQUEST_BUDGET = 50;
22
22
  const DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET = 20;
23
+ const DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET = 4;
23
24
  const MAX_ACCEPTED_NUMERIC_LIMIT = 1_000;
24
25
  const PROGRAM_COLLECTION_CONCURRENCY = 4;
25
26
  const TARGET_SAMPLE_CONCURRENCY = 3;
@@ -30,10 +31,12 @@ const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
30
31
  const MAX_LOCAL_EXPORT_RESOURCES = 25;
31
32
  const EXPORT_PREVIEW_LIMIT = 25;
32
33
  const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
33
- const STALE_PROGRAM_ID_RESOLVE_BUDGET = 1;
34
+ const STALE_PROGRAM_ID_RESOLVE_BUDGET = 8;
34
35
  const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
35
36
  const MAX_RESOLVE_SEARCH_QUERIES = 4;
37
+ const MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS = 8;
36
38
  const SDK_VERSION = "1.29.0";
39
+ const PROGRAM_ID_PLATFORM_GUESSES = ["HackerOne", "Bugcrowd", "Intigriti", "HackenProof", "YesWeHack", "Cantina", "Code4rena", "Immunefi"];
37
40
  const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
38
41
  const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
39
42
  const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
@@ -44,6 +47,15 @@ const PROGRAM_RESOLUTION_STOP_WORDS = new Set([
44
47
  "targets",
45
48
  "scope",
46
49
  "scopes",
50
+ "network",
51
+ "networks",
52
+ "protocol",
53
+ "protocols",
54
+ "bug",
55
+ "bugs",
56
+ "bounty",
57
+ "bounties",
58
+ "security",
47
59
  "new",
48
60
  "latest",
49
61
  "recent",
@@ -70,13 +82,51 @@ const PROGRAM_RESOLUTION_STOP_WORDS = new Set([
70
82
  "have",
71
83
  "any"
72
84
  ]);
85
+ const TARGET_QUERY_STOP_WORDS = new Set([
86
+ "program",
87
+ "programs",
88
+ "target",
89
+ "targets",
90
+ "scope",
91
+ "scopes",
92
+ "asset",
93
+ "assets",
94
+ "endpoint",
95
+ "endpoints",
96
+ "url",
97
+ "urls",
98
+ "http",
99
+ "https",
100
+ "www",
101
+ "the",
102
+ "a",
103
+ "an",
104
+ "of",
105
+ "on",
106
+ "in",
107
+ "with",
108
+ "to",
109
+ "for",
110
+ "find",
111
+ "show",
112
+ "lookup",
113
+ "look",
114
+ "up",
115
+ "com",
116
+ "org",
117
+ "net",
118
+ "io"
119
+ ]);
73
120
  const targetListModeSchema = z.enum(["identifiers", "compact", "full"]);
74
121
  const changeListModeSchema = z.enum(["compact", "full"]);
75
122
  const programListModeSchema = z.enum(["compact", "full"]);
76
123
  const rewardThresholdModeSchema = z.enum(["max_at_least", "min_at_least"]);
77
124
  const vdpModeSchema = z.enum(["include", "exclude", "only_likely", "only_known_no_bounty"]);
78
- const targetMatchModeSchema = z.enum(["contains", "exact", "suffix", "wildcard"]);
125
+ const targetMatchModeSchema = z
126
+ .enum(["smart", "contains", "exact", "suffix", "wildcard"])
127
+ .describe("How to match target_query. smart is forgiving for URLs, wildcards, target names, and partial words.");
79
128
  const programNameActionSchema = z.enum(["new_targets", "targets", "scope_delta", "brief", "target_breakdown"]);
129
+ const guideTopicSchema = z.enum(["start", "program", "discovery", "freshness", "targets", "compare", "export", "filters"]).default("start");
80
130
  const readOnlyAnnotations = {
81
131
  readOnlyHint: true,
82
132
  destructiveHint: false,
@@ -84,17 +134,36 @@ const readOnlyAnnotations = {
84
134
  openWorldHint: true
85
135
  };
86
136
  const TOOL_METRICS = new Map();
87
- const filterValueSchema = z
88
- .string()
89
- .trim()
90
- .min(1)
91
- .max(128)
92
- .refine((value) => !/[,\u0000-\u001F\u007F]/.test(value), {
93
- message: "Filter values cannot contain commas or control characters."
94
- });
95
- const stringListSchema = z
96
- .preprocess(normalizeStringListInput, z.array(filterValueSchema).max(50).default([]))
97
- .describe("Optional filter values. Empty or omitted means all values; do not infer a platform filter.");
137
+ function filterValueSchema() {
138
+ return z
139
+ .string()
140
+ .trim()
141
+ .min(1)
142
+ .max(128)
143
+ .refine((value) => !/[,\u0000-\u001F\u007F]/.test(value), {
144
+ message: "Filter values cannot contain commas or control characters."
145
+ });
146
+ }
147
+ function stringListSchema(description = "Optional filter values. Empty or omitted means all values; do not infer a platform filter.") {
148
+ return z
149
+ .preprocess(normalizeStringListInput, z.array(filterValueSchema()).max(50).default([]))
150
+ .describe(description);
151
+ }
152
+ function platformListSchema() {
153
+ return stringListSchema("Optional platform filters such as HackerOne, Bugcrowd, Intigriti, or HackenProof. Empty or omitted means all platforms.");
154
+ }
155
+ function tagListSchema() {
156
+ return stringListSchema("Optional combined scope/language/target-type tags. Empty or omitted means all tags.");
157
+ }
158
+ function scopeTagListSchema() {
159
+ return stringListSchema("Optional scope tags such as domain, api, mobile, smart-contract, or wildcard. Empty or omitted means all scope tags.");
160
+ }
161
+ function targetTypeListSchema() {
162
+ return stringListSchema("Optional target-type tags such as domain, api, mobile, smart-contract, url, or wildcard. Empty or omitted means all target types.");
163
+ }
164
+ function languageTagListSchema() {
165
+ return stringListSchema("Optional language or technology tags such as javascript, solidity, web3, php, or python. Empty or omitted means all languages.");
166
+ }
98
167
  const acceptedTargetLimitSchema = z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM);
99
168
  const searchTextSchema = z
100
169
  .string()
@@ -104,6 +173,15 @@ const searchTextSchema = z
104
173
  .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
105
174
  message: "Search text cannot contain control characters."
106
175
  });
176
+ const targetQuerySchema = z
177
+ .string()
178
+ .trim()
179
+ .min(2)
180
+ .max(512)
181
+ .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
182
+ message: "Target query cannot contain control characters."
183
+ })
184
+ .describe("Target identifier, domain, URL, wildcard, app/package/repo name, or partial target text. URLs and wildcards are normalized automatically.");
107
185
  const isoDateTimeSchema = z.string().datetime({ offset: true });
108
186
  const pageSchema = z.number().int().min(1).default(1);
109
187
  const programsPageSizeSchema = z.number().int().min(1).max(MAX_PROGRAMS_PER_SEARCH).default(20);
@@ -133,69 +211,97 @@ const programIdSchema = z
133
211
  message: "program_id cannot contain control characters."
134
212
  })
135
213
  .describe("platform:handle id.");
136
- const jsonRecordSchema = z.record(z.unknown());
137
- const rateLimitOutputSchema = z.object({
138
- remaining: z.number().int().min(0),
139
- reset_at: z.string()
140
- });
141
- const toolTimingOutputSchema = z.object({
142
- tool_name: z.string(),
143
- duration_ms: z.number().int().min(0)
144
- });
145
- const cacheOutputSchema = z.object({
146
- hit: z.boolean(),
147
- coalesced_live_request: z.boolean().optional(),
148
- expires_at: z.string().optional()
149
- });
150
- const errorOutputSchema = z.object({
151
- message: z.string(),
152
- status: z.number().int().optional(),
153
- detail: z.unknown().optional(),
154
- errors: z.unknown().optional()
155
- });
156
- const programOutputSchema = jsonRecordSchema;
157
- const targetOutputSchema = jsonRecordSchema;
158
- const apiEnvelopeOutputShape = {
159
- request_id: z.string(),
160
- upstream_request_id: z.string().optional(),
161
- fetched_at: z.string().optional(),
162
- cache: cacheOutputSchema.optional(),
163
- source_requests: z.array(jsonRecordSchema).optional(),
164
- mcp_rate_limit: rateLimitOutputSchema.optional(),
165
- mcp_timing: toolTimingOutputSchema.optional(),
166
- warnings: z.array(z.string()).optional(),
167
- program_id_resolution: jsonRecordSchema.optional(),
168
- error: errorOutputSchema.optional()
169
- };
170
- const programListOutputShape = {
171
- ...apiEnvelopeOutputShape,
172
- programs: z.array(programOutputSchema).optional(),
173
- meta: jsonRecordSchema.optional()
174
- };
175
- const findProgramsOutputShape = {
176
- ...programListOutputShape,
177
- source_requests: z.array(jsonRecordSchema).optional(),
178
- filters: jsonRecordSchema.optional(),
179
- warnings: z.array(z.string()).optional(),
180
- errors: z.array(jsonRecordSchema).optional(),
181
- upstream_budget: jsonRecordSchema.optional(),
182
- ranking_scope: jsonRecordSchema.optional()
183
- };
184
- const decisionOutputShape = {
185
- ...findProgramsOutputShape,
186
- partial_success: z.boolean().optional(),
187
- program_ids_requested: z.array(z.string()).optional(),
188
- failed_program_ids: z.array(z.string()).optional(),
189
- compared_programs: z.array(programOutputSchema).optional(),
190
- activity: jsonRecordSchema.optional(),
191
- changes: z.array(jsonRecordSchema).optional()
192
- };
214
+ const programIdentifierSchema = z
215
+ .string()
216
+ .trim()
217
+ .min(2)
218
+ .max(256)
219
+ .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
220
+ message: "Program identifiers cannot contain control characters."
221
+ })
222
+ .describe("BBRadar platform:handle program_id or a human program name/handle. Names are resolved automatically.");
223
+ function jsonRecordSchema() {
224
+ return z.record(z.unknown());
225
+ }
226
+ function rateLimitOutputSchema() {
227
+ return z.object({
228
+ remaining: z.number().int().min(0),
229
+ reset_at: z.string()
230
+ });
231
+ }
232
+ function toolTimingOutputSchema() {
233
+ return z.object({
234
+ tool_name: z.string(),
235
+ duration_ms: z.number().int().min(0)
236
+ });
237
+ }
238
+ function cacheOutputSchema() {
239
+ return z.object({
240
+ hit: z.boolean(),
241
+ coalesced_live_request: z.boolean().optional(),
242
+ expires_at: z.string().optional()
243
+ });
244
+ }
245
+ function errorOutputSchema() {
246
+ return z.object({
247
+ message: z.string(),
248
+ status: z.number().int().optional(),
249
+ detail: z.unknown().optional(),
250
+ errors: z.unknown().optional()
251
+ });
252
+ }
253
+ function programOutputSchema() {
254
+ return jsonRecordSchema();
255
+ }
256
+ function apiEnvelopeOutputShape() {
257
+ return {
258
+ request_id: z.string(),
259
+ upstream_request_id: z.string().optional(),
260
+ fetched_at: z.string().optional(),
261
+ cache: cacheOutputSchema().optional(),
262
+ source_requests: z.array(jsonRecordSchema()).optional(),
263
+ mcp_rate_limit: rateLimitOutputSchema().optional(),
264
+ mcp_timing: toolTimingOutputSchema().optional(),
265
+ warnings: z.array(z.string()).optional(),
266
+ program_id_resolution: jsonRecordSchema().optional(),
267
+ error: errorOutputSchema().optional()
268
+ };
269
+ }
270
+ function programListOutputShape() {
271
+ return {
272
+ ...apiEnvelopeOutputShape(),
273
+ programs: z.array(programOutputSchema()).optional(),
274
+ meta: jsonRecordSchema().optional()
275
+ };
276
+ }
277
+ function findProgramsOutputShape() {
278
+ return {
279
+ ...programListOutputShape(),
280
+ source_requests: z.array(jsonRecordSchema()).optional(),
281
+ filters: jsonRecordSchema().optional(),
282
+ warnings: z.array(z.string()).optional(),
283
+ errors: z.array(jsonRecordSchema()).optional(),
284
+ upstream_budget: jsonRecordSchema().optional(),
285
+ ranking_scope: jsonRecordSchema().optional()
286
+ };
287
+ }
288
+ function decisionOutputShape() {
289
+ return {
290
+ ...findProgramsOutputShape(),
291
+ partial_success: z.boolean().optional(),
292
+ program_ids_requested: z.array(z.string()).optional(),
293
+ failed_program_ids: z.array(z.string()).optional(),
294
+ compared_programs: z.array(programOutputSchema()).optional(),
295
+ activity: jsonRecordSchema().optional(),
296
+ changes: z.array(jsonRecordSchema()).optional()
297
+ };
298
+ }
193
299
  const programCandidatePresetInputSchema = {
194
- platforms: stringListSchema,
195
- tags: stringListSchema,
196
- scope_tags: stringListSchema,
197
- target_types: stringListSchema,
198
- language_tags: stringListSchema,
300
+ platforms: platformListSchema(),
301
+ tags: tagListSchema(),
302
+ scope_tags: scopeTagListSchema(),
303
+ target_types: targetTypeListSchema(),
304
+ language_tags: languageTagListSchema(),
199
305
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default(["elite", "hot", "strong", "potential"]),
200
306
  web3: z.boolean().default(false),
201
307
  wildcard: z.boolean().default(false),
@@ -225,7 +331,9 @@ export function createBbradarServer(client, config) {
225
331
  }, {
226
332
  instructions: [
227
333
  "Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
228
- "For BBRadar asks, use these tools first and avoid external skills unless methodology is explicitly requested.",
334
+ "When unsure which BBRadar tool to use, call get_bbradar_guide first. It is local-only and cheap.",
335
+ "Simple default path: find_program_candidates for discovery, run_program_name_action for one named program, get_program_eligible_targets_by_name for target lists by program name.",
336
+ "Use advanced tools only after the user's goal is clear or the guide points to them.",
229
337
  "Partial program name + action: run_program_name_action. Partial program name only: resolve_program.",
230
338
  "Default to bounty-eligible in-scope data. Include out-of-scope, ineligible, VDP, disclosure-only, or no-bounty data only when the user explicitly asks.",
231
339
  "Recently updated programs across platforms: find_recently_updated_programs. Do not infer a platform such as Immunefi unless the user explicitly names it.",
@@ -241,14 +349,14 @@ export function createBbradarServer(client, config) {
241
349
  title: "Search BBRadar Programs",
242
350
  description: `List active programs. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
243
351
  inputSchema: {
244
- platforms: stringListSchema,
245
- tags: stringListSchema,
352
+ platforms: platformListSchema(),
353
+ tags: tagListSchema(),
246
354
  updated_since: isoDateTimeSchema.optional().describe("ISO datetime."),
247
355
  vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
248
356
  page: pageSchema,
249
357
  page_size: programsPageSizeSchema
250
358
  },
251
- outputSchema: programListOutputShape,
359
+ outputSchema: programListOutputShape(),
252
360
  annotations: readOnlyAnnotations
253
361
  }, (args) => runTool("search_programs", config, rateLimiter, async () => {
254
362
  const { vdp_mode, ...filters } = args;
@@ -265,14 +373,14 @@ export function createBbradarServer(client, config) {
265
373
  }));
266
374
  server.registerTool("get_program", {
267
375
  title: "Get BBRadar Program",
268
- description: "Get one program by id.",
376
+ description: "Get one program by platform:handle program_id, handle, or human program name. Names are resolved automatically when unambiguous.",
269
377
  inputSchema: {
270
- program_id: programIdSchema
378
+ program_id: programIdentifierSchema
271
379
  },
272
380
  outputSchema: {
273
- ...apiEnvelopeOutputShape,
274
- program: programOutputSchema.optional(),
275
- meta: jsonRecordSchema.optional()
381
+ ...apiEnvelopeOutputShape(),
382
+ program: programOutputSchema().optional(),
383
+ meta: jsonRecordSchema().optional()
276
384
  },
277
385
  annotations: readOnlyAnnotations
278
386
  }, (args) => runTool("get_program", config, rateLimiter, async () => {
@@ -304,37 +412,37 @@ export function createBbradarServer(client, config) {
304
412
  description: "Resolve partial name/handle text to likely program_ids. Use this when the user gives a program name fragment like Nado.",
305
413
  inputSchema: {
306
414
  query: searchTextSchema,
307
- platforms: stringListSchema,
308
- tags: stringListSchema,
415
+ platforms: platformListSchema(),
416
+ tags: tagListSchema(),
309
417
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
310
418
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
311
419
  max_results: z.number().int().min(1).max(25).default(10),
312
420
  output_mode: programListModeSchema.default("compact"),
313
- upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
421
+ upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET)
314
422
  },
315
423
  outputSchema: {
316
- ...programListOutputShape,
317
- source_requests: z.array(jsonRecordSchema).optional(),
318
- query: jsonRecordSchema.optional(),
319
- matches: z.array(jsonRecordSchema).optional(),
424
+ ...programListOutputShape(),
425
+ source_requests: z.array(jsonRecordSchema()).optional(),
426
+ query: jsonRecordSchema().optional(),
427
+ matches: z.array(jsonRecordSchema()).optional(),
320
428
  warnings: z.array(z.string()).optional(),
321
- upstream_budget: jsonRecordSchema.optional(),
322
- ranking_scope: jsonRecordSchema.optional()
429
+ upstream_budget: jsonRecordSchema().optional(),
430
+ ranking_scope: jsonRecordSchema().optional()
323
431
  },
324
432
  annotations: readOnlyAnnotations
325
433
  }, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
326
434
  server.registerTool("run_program_name_action", {
327
435
  title: "Run Program Name Action",
328
- description: "Resolve a partial program name, then run targets, new_targets, scope_delta, brief, or target_breakdown.",
436
+ description: "Start here for one named program. Resolves a partial program name, then runs targets, new_targets, scope_delta, brief, or target_breakdown.",
329
437
  inputSchema: {
330
438
  query: searchTextSchema,
331
439
  action: programNameActionSchema.default("new_targets"),
332
- platforms: stringListSchema,
333
- tags: stringListSchema,
440
+ platforms: platformListSchema(),
441
+ tags: tagListSchema(),
334
442
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
335
443
  since: isoDateTimeSchema.optional(),
336
- target_type: filterValueSchema.optional(),
337
- language_tags: stringListSchema,
444
+ target_type: filterValueSchema().optional(),
445
+ language_tags: languageTagListSchema(),
338
446
  include_out_of_scope: z.boolean().default(false),
339
447
  include_ineligible: z.boolean().default(false),
340
448
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
@@ -343,17 +451,17 @@ export function createBbradarServer(client, config) {
343
451
  target_list_mode: targetListModeSchema.default("identifiers"),
344
452
  max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
345
453
  output_mode: programListModeSchema.default("compact"),
346
- upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
454
+ upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET)
347
455
  },
348
456
  outputSchema: {
349
- ...apiEnvelopeOutputShape,
350
- source_requests: z.array(jsonRecordSchema).optional(),
351
- resolved_program: jsonRecordSchema.optional(),
457
+ ...apiEnvelopeOutputShape(),
458
+ source_requests: z.array(jsonRecordSchema()).optional(),
459
+ resolved_program: jsonRecordSchema().optional(),
352
460
  action: z.string().optional(),
353
- result: jsonRecordSchema.optional(),
461
+ result: jsonRecordSchema().optional(),
354
462
  warnings: z.array(z.string()).optional(),
355
- upstream_budget: jsonRecordSchema.optional(),
356
- ranking_scope: jsonRecordSchema.optional()
463
+ upstream_budget: jsonRecordSchema().optional(),
464
+ ranking_scope: jsonRecordSchema().optional()
357
465
  },
358
466
  annotations: readOnlyAnnotations
359
467
  }, (args) => runTool("run_program_name_action", config, rateLimiter, async () => runProgramNameAction(client, config, args)));
@@ -362,11 +470,11 @@ export function createBbradarServer(client, config) {
362
470
  description: "Fast one-call path for user asks like 'find in-scope eligible targets for Aztec': resolve the program name, fetch targets, and return filtered in-scope bounty-eligible targets. Prefer this over separate resolve_program + get_program_targets calls when the user asks for a target list by name.",
363
471
  inputSchema: {
364
472
  query: searchTextSchema,
365
- platforms: stringListSchema,
366
- tags: stringListSchema,
473
+ platforms: platformListSchema(),
474
+ tags: tagListSchema(),
367
475
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
368
476
  max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
369
- upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET),
477
+ upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET),
370
478
  include_out_of_scope: z.boolean().default(false),
371
479
  include_ineligible: z.boolean().default(false),
372
480
  strict_scope_filter: z.boolean().default(true),
@@ -376,23 +484,23 @@ export function createBbradarServer(client, config) {
376
484
  output_mode: programListModeSchema.default("compact")
377
485
  },
378
486
  outputSchema: {
379
- ...apiEnvelopeOutputShape,
380
- source_requests: z.array(jsonRecordSchema).optional(),
381
- resolved_program: jsonRecordSchema.optional(),
487
+ ...apiEnvelopeOutputShape(),
488
+ source_requests: z.array(jsonRecordSchema()).optional(),
489
+ resolved_program: jsonRecordSchema().optional(),
382
490
  program_id: z.string().optional(),
383
491
  targets: z.array(z.unknown()).optional(),
384
492
  warnings: z.array(z.string()).optional(),
385
- upstream_budget: jsonRecordSchema.optional(),
386
- ranking_scope: jsonRecordSchema.optional(),
387
- meta: jsonRecordSchema.optional()
493
+ upstream_budget: jsonRecordSchema().optional(),
494
+ ranking_scope: jsonRecordSchema().optional(),
495
+ meta: jsonRecordSchema().optional()
388
496
  },
389
497
  annotations: readOnlyAnnotations
390
498
  }, (args) => runTool("get_program_eligible_targets_by_name", config, rateLimiter, async () => getProgramEligibleTargetsByName(client, config, args)));
391
499
  server.registerTool("get_program_targets", {
392
500
  title: "Get Program Targets",
393
- description: `Get active targets for one known program_id. This is a final source for eligible target lists; do not call summary/breakdown unless grouped counts are needed. For a program name query, prefer get_program_eligible_targets_by_name. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
501
+ description: `Get active targets for one program_id, handle, or human program name. This is a final source for eligible target lists; do not call summary/breakdown unless grouped counts are needed. For a program name query, get_program_eligible_targets_by_name is the simplest path. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
394
502
  inputSchema: {
395
- program_id: programIdSchema,
503
+ program_id: programIdentifierSchema,
396
504
  include_out_of_scope: z.boolean().default(false),
397
505
  include_ineligible: z.boolean().default(false),
398
506
  strict_scope_filter: z.boolean().default(true),
@@ -401,10 +509,10 @@ export function createBbradarServer(client, config) {
401
509
  output_mode: targetListModeSchema.default("compact")
402
510
  },
403
511
  outputSchema: {
404
- ...apiEnvelopeOutputShape,
512
+ ...apiEnvelopeOutputShape(),
405
513
  program_id: z.string().optional(),
406
514
  targets: z.array(z.unknown()).optional(),
407
- meta: jsonRecordSchema.optional()
515
+ meta: jsonRecordSchema().optional()
408
516
  },
409
517
  annotations: readOnlyAnnotations
410
518
  }, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
@@ -419,17 +527,17 @@ export function createBbradarServer(client, config) {
419
527
  include_ineligible: z.boolean().default(false),
420
528
  include_out_of_scope: z.boolean().default(false),
421
529
  search: searchTextSchema.optional(),
422
- platforms: stringListSchema,
423
- tags: stringListSchema,
530
+ platforms: platformListSchema(),
531
+ tags: tagListSchema(),
424
532
  page: pageSchema,
425
533
  page_size: recentChangesPageSizeSchema,
426
534
  output_mode: changeListModeSchema.default("compact")
427
535
  },
428
536
  outputSchema: {
429
- ...apiEnvelopeOutputShape,
430
- changes: z.array(jsonRecordSchema).optional(),
431
- meta: jsonRecordSchema.optional(),
432
- facets: jsonRecordSchema.optional()
537
+ ...apiEnvelopeOutputShape(),
538
+ changes: z.array(jsonRecordSchema()).optional(),
539
+ meta: jsonRecordSchema().optional(),
540
+ facets: jsonRecordSchema().optional()
433
541
  },
434
542
  annotations: readOnlyAnnotations
435
543
  }, (args) => runTool("get_recent_changes", config, rateLimiter, async () => {
@@ -453,7 +561,7 @@ export function createBbradarServer(client, config) {
453
561
  title: "Get Latest Added Targets",
454
562
  description: "Latest added targets by type, optionally with that program's target list.",
455
563
  inputSchema: {
456
- target_type: filterValueSchema.default("domain"),
564
+ target_type: filterValueSchema().default("domain"),
457
565
  include_out_of_scope: z.boolean().default(false).describe("Include out-of-scope."),
458
566
  include_ineligible: z.boolean().default(false).describe("Include ineligible."),
459
567
  recent_changes_page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
@@ -464,14 +572,14 @@ export function createBbradarServer(client, config) {
464
572
  full_list_include_ineligible: z.boolean().default(false)
465
573
  },
466
574
  outputSchema: {
467
- ...apiEnvelopeOutputShape,
468
- source_requests: z.array(jsonRecordSchema).optional(),
469
- query: jsonRecordSchema.optional(),
575
+ ...apiEnvelopeOutputShape(),
576
+ source_requests: z.array(jsonRecordSchema()).optional(),
577
+ query: jsonRecordSchema().optional(),
470
578
  latest_added_at: z.string().optional(),
471
- program: programOutputSchema.optional(),
579
+ program: programOutputSchema().optional(),
472
580
  new_targets: z.array(z.unknown()).optional(),
473
581
  full_targets: z.array(z.unknown()).optional(),
474
- meta: jsonRecordSchema.optional()
582
+ meta: jsonRecordSchema().optional()
475
583
  },
476
584
  annotations: readOnlyAnnotations
477
585
  }, (args) => runTool("get_latest_added_targets", config, rateLimiter, async () => getLatestAddedTargets(client, config, args)));
@@ -479,18 +587,18 @@ export function createBbradarServer(client, config) {
479
587
  title: "Get Program Scope Summary",
480
588
  description: "Grouped compact scope for one program.",
481
589
  inputSchema: {
482
- program_id: programIdSchema,
590
+ program_id: programIdentifierSchema,
483
591
  target_list_mode: targetListModeSchema.default("identifiers"),
484
592
  group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
485
593
  include_out_of_scope: z.boolean().default(false),
486
594
  include_ineligible: z.boolean().default(false)
487
595
  },
488
596
  outputSchema: {
489
- ...apiEnvelopeOutputShape,
490
- source_requests: z.array(jsonRecordSchema).optional(),
491
- program: programOutputSchema.optional(),
492
- scope: jsonRecordSchema.optional(),
493
- meta: jsonRecordSchema.optional()
597
+ ...apiEnvelopeOutputShape(),
598
+ source_requests: z.array(jsonRecordSchema()).optional(),
599
+ program: programOutputSchema().optional(),
600
+ scope: jsonRecordSchema().optional(),
601
+ meta: jsonRecordSchema().optional()
494
602
  },
495
603
  annotations: readOnlyAnnotations
496
604
  }, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeSummaryFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
@@ -498,18 +606,18 @@ export function createBbradarServer(client, config) {
498
606
  title: "Get Program Target Breakdown",
499
607
  description: "Target type, scope tag, language tag, and bucket counts for one program.",
500
608
  inputSchema: {
501
- program_id: programIdSchema,
609
+ program_id: programIdentifierSchema,
502
610
  target_list_mode: targetListModeSchema.default("identifiers"),
503
611
  group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
504
612
  include_out_of_scope: z.boolean().default(false),
505
613
  include_ineligible: z.boolean().default(false)
506
614
  },
507
615
  outputSchema: {
508
- ...apiEnvelopeOutputShape,
509
- source_requests: z.array(jsonRecordSchema).optional(),
510
- program: programOutputSchema.optional(),
511
- breakdown: jsonRecordSchema.optional(),
512
- meta: jsonRecordSchema.optional()
616
+ ...apiEnvelopeOutputShape(),
617
+ source_requests: z.array(jsonRecordSchema()).optional(),
618
+ program: programOutputSchema().optional(),
619
+ breakdown: jsonRecordSchema().optional(),
620
+ meta: jsonRecordSchema().optional()
513
621
  },
514
622
  annotations: readOnlyAnnotations
515
623
  }, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramTargetBreakdownFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
@@ -517,11 +625,11 @@ export function createBbradarServer(client, config) {
517
625
  title: "Get Program Scope Delta",
518
626
  description: "Compact recent scope changes for one program.",
519
627
  inputSchema: {
520
- program_id: programIdSchema,
628
+ program_id: programIdentifierSchema,
521
629
  since: isoDateTimeSchema.optional(),
522
630
  change_type: z.enum(["added", "updated", "removed"]).optional(),
523
- target_type: filterValueSchema.optional(),
524
- language_tags: stringListSchema,
631
+ target_type: filterValueSchema().optional(),
632
+ language_tags: languageTagListSchema(),
525
633
  include_removed: z.boolean().default(true),
526
634
  include_out_of_scope: z.boolean().default(false),
527
635
  include_ineligible: z.boolean().default(false),
@@ -530,12 +638,12 @@ export function createBbradarServer(client, config) {
530
638
  target_list_mode: targetListModeSchema.default("identifiers")
531
639
  },
532
640
  outputSchema: {
533
- ...apiEnvelopeOutputShape,
534
- source_requests: z.array(jsonRecordSchema).optional(),
535
- program: programOutputSchema.optional(),
536
- delta: jsonRecordSchema.optional(),
537
- changes: z.array(jsonRecordSchema).optional(),
538
- meta: jsonRecordSchema.optional()
641
+ ...apiEnvelopeOutputShape(),
642
+ source_requests: z.array(jsonRecordSchema()).optional(),
643
+ program: programOutputSchema().optional(),
644
+ delta: jsonRecordSchema().optional(),
645
+ changes: z.array(jsonRecordSchema()).optional(),
646
+ meta: jsonRecordSchema().optional()
539
647
  },
540
648
  annotations: readOnlyAnnotations
541
649
  }, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeDeltaFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
@@ -544,24 +652,24 @@ export function createBbradarServer(client, config) {
544
652
  description: "Recent target changes grouped by program.",
545
653
  inputSchema: {
546
654
  change_type: z.enum(["added", "updated", "removed"]).optional(),
547
- target_type: filterValueSchema.optional(),
655
+ target_type: filterValueSchema().optional(),
548
656
  include_removed: z.boolean().default(true),
549
657
  include_out_of_scope: z.boolean().default(false),
550
658
  include_ineligible: z.boolean().default(false),
551
659
  search: searchTextSchema.optional(),
552
- platforms: stringListSchema,
553
- tags: stringListSchema,
554
- language_tags: stringListSchema,
660
+ platforms: platformListSchema(),
661
+ tags: tagListSchema(),
662
+ language_tags: languageTagListSchema(),
555
663
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
556
664
  max_programs: z.number().int().min(1).max(50).default(10),
557
665
  sample_size: z.number().int().min(1).max(20).default(5),
558
666
  target_list_mode: targetListModeSchema.default("identifiers")
559
667
  },
560
668
  outputSchema: {
561
- ...apiEnvelopeOutputShape,
562
- source_requests: z.array(jsonRecordSchema).optional(),
563
- activity: z.array(jsonRecordSchema).optional(),
564
- meta: jsonRecordSchema.optional()
669
+ ...apiEnvelopeOutputShape(),
670
+ source_requests: z.array(jsonRecordSchema()).optional(),
671
+ activity: z.array(jsonRecordSchema()).optional(),
672
+ meta: jsonRecordSchema().optional()
565
673
  },
566
674
  annotations: readOnlyAnnotations
567
675
  }, (args) => runTool("get_recent_target_activity", config, rateLimiter, async () => getRecentTargetActivity(client, config, args)));
@@ -569,24 +677,24 @@ export function createBbradarServer(client, config) {
569
677
  title: "Check Program New Targets",
570
678
  description: "Yes/no new in-scope targets for one program.",
571
679
  inputSchema: {
572
- program_id: programIdSchema,
680
+ program_id: programIdentifierSchema,
573
681
  since: isoDateTimeSchema.optional().describe("ISO datetime."),
574
- target_type: filterValueSchema.optional(),
682
+ target_type: filterValueSchema().optional(),
575
683
  include_ineligible: z.boolean().default(false).describe("Include ineligible."),
576
684
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
577
685
  max_targets: acceptedTargetLimitSchema.default(25),
578
686
  target_list_mode: targetListModeSchema.default("identifiers")
579
687
  },
580
688
  outputSchema: {
581
- ...apiEnvelopeOutputShape,
582
- source_requests: z.array(jsonRecordSchema).optional(),
689
+ ...apiEnvelopeOutputShape(),
690
+ source_requests: z.array(jsonRecordSchema()).optional(),
583
691
  program_id: z.string().optional(),
584
- program: programOutputSchema.optional(),
692
+ program: programOutputSchema().optional(),
585
693
  has_new_targets: z.boolean().optional(),
586
694
  new_target_count: z.number().int().min(0).optional(),
587
695
  latest_added_at: z.string().optional(),
588
696
  new_targets: z.array(z.unknown()).optional(),
589
- meta: jsonRecordSchema.optional()
697
+ meta: jsonRecordSchema().optional()
590
698
  },
591
699
  annotations: readOnlyAnnotations
592
700
  }, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => checkProgramNewTargets(client, config, { ...args, program_id: programId }), (indexFallback) => checkProgramNewTargetsFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
@@ -594,20 +702,20 @@ export function createBbradarServer(client, config) {
594
702
  title: "Check Watchlist New Targets",
595
703
  description: "New in-scope targets across many known program_ids.",
596
704
  inputSchema: {
597
- program_ids: z.array(programIdSchema).min(1).max(50),
705
+ program_ids: z.array(programIdentifierSchema).min(1).max(50),
598
706
  since: isoDateTimeSchema.optional(),
599
- target_type: filterValueSchema.optional(),
707
+ target_type: filterValueSchema().optional(),
600
708
  include_ineligible: z.boolean().default(false),
601
709
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
602
710
  max_targets_per_program: z.number().int().min(1).max(50).default(10),
603
711
  target_list_mode: targetListModeSchema.default("identifiers")
604
712
  },
605
713
  outputSchema: {
606
- ...apiEnvelopeOutputShape,
607
- source_requests: z.array(jsonRecordSchema).optional(),
608
- programs: z.array(jsonRecordSchema).optional(),
714
+ ...apiEnvelopeOutputShape(),
715
+ source_requests: z.array(jsonRecordSchema()).optional(),
716
+ programs: z.array(jsonRecordSchema()).optional(),
609
717
  any_new_targets: z.boolean().optional(),
610
- meta: jsonRecordSchema.optional()
718
+ meta: jsonRecordSchema().optional()
611
719
  },
612
720
  annotations: readOnlyAnnotations
613
721
  }, (args) => runTool("check_watchlist_new_targets", config, rateLimiter, async () => checkWatchlistNewTargets(client, config, args)));
@@ -616,15 +724,15 @@ export function createBbradarServer(client, config) {
616
724
  description: `List programs in one opportunity tier. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
617
725
  inputSchema: {
618
726
  level: opportunityLevelSchema.default("elite"),
619
- platforms: stringListSchema,
620
- tags: stringListSchema,
727
+ platforms: platformListSchema(),
728
+ tags: tagListSchema(),
621
729
  updated_since: isoDateTimeSchema.optional(),
622
730
  vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
623
731
  page: pageSchema,
624
732
  page_size: programsPageSizeSchema
625
733
  },
626
734
  outputSchema: {
627
- ...programListOutputShape,
735
+ ...programListOutputShape(),
628
736
  level: opportunityLevelSchema.optional()
629
737
  },
630
738
  annotations: readOnlyAnnotations
@@ -646,11 +754,11 @@ export function createBbradarServer(client, config) {
646
754
  title: "Find BBRadar Programs",
647
755
  description: "Ranked program discovery with filters and compact output.",
648
756
  inputSchema: {
649
- platforms: stringListSchema,
650
- tags: stringListSchema,
651
- scope_tags: stringListSchema.describe("Scope tags."),
652
- target_types: stringListSchema.describe("Target-type tags."),
653
- language_tags: stringListSchema.describe("Language tags."),
757
+ platforms: platformListSchema(),
758
+ tags: tagListSchema(),
759
+ scope_tags: scopeTagListSchema(),
760
+ target_types: targetTypeListSchema(),
761
+ language_tags: languageTagListSchema(),
654
762
  web3: z.boolean().default(false).describe("Require Web3 signals."),
655
763
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
656
764
  updated_since: isoDateTimeSchema.optional(),
@@ -684,7 +792,7 @@ export function createBbradarServer(client, config) {
684
792
  output_mode: programListModeSchema.default("compact"),
685
793
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
686
794
  },
687
- outputSchema: findProgramsOutputShape,
795
+ outputSchema: findProgramsOutputShape(),
688
796
  annotations: readOnlyAnnotations
689
797
  }, (args) => runTool("find_programs", config, rateLimiter, async () => findPrograms(client, config, {
690
798
  ...args,
@@ -694,8 +802,8 @@ export function createBbradarServer(client, config) {
694
802
  title: "Find Wildcard Programs",
695
803
  description: "Wildcard program shortlist.",
696
804
  inputSchema: {
697
- platforms: stringListSchema,
698
- tags: stringListSchema,
805
+ platforms: platformListSchema(),
806
+ tags: tagListSchema(),
699
807
  updated_since: isoDateTimeSchema.optional(),
700
808
  max_public_report_count: z.number().int().min(0).default(25),
701
809
  require_known_report_count: z.boolean().default(true),
@@ -710,7 +818,7 @@ export function createBbradarServer(client, config) {
710
818
  target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
711
819
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
712
820
  },
713
- outputSchema: findProgramsOutputShape,
821
+ outputSchema: findProgramsOutputShape(),
714
822
  annotations: readOnlyAnnotations
715
823
  }, (args) => runTool("find_wildcard_programs", config, rateLimiter, async () => findPrograms(client, config, {
716
824
  platforms: args.platforms,
@@ -754,8 +862,8 @@ export function createBbradarServer(client, config) {
754
862
  title: "Find Web3 Programs",
755
863
  description: "Web3/blockchain/smart-contract program shortlist.",
756
864
  inputSchema: {
757
- platforms: stringListSchema,
758
- tags: stringListSchema,
865
+ platforms: platformListSchema(),
866
+ tags: tagListSchema(),
759
867
  updated_since: isoDateTimeSchema.optional(),
760
868
  max_public_report_count: z.number().int().min(0).optional(),
761
869
  require_known_report_count: z.boolean().default(false),
@@ -771,7 +879,7 @@ export function createBbradarServer(client, config) {
771
879
  target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
772
880
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
773
881
  },
774
- outputSchema: findProgramsOutputShape,
882
+ outputSchema: findProgramsOutputShape(),
775
883
  annotations: readOnlyAnnotations
776
884
  }, (args) => runTool("find_web3_programs", config, rateLimiter, async () => findPrograms(client, config, {
777
885
  platforms: args.platforms,
@@ -815,10 +923,10 @@ export function createBbradarServer(client, config) {
815
923
  title: "Find Reward Programs",
816
924
  description: "Programs matching reward thresholds.",
817
925
  inputSchema: {
818
- platforms: stringListSchema,
819
- tags: stringListSchema,
820
- target_types: stringListSchema,
821
- language_tags: stringListSchema,
926
+ platforms: platformListSchema(),
927
+ tags: tagListSchema(),
928
+ target_types: targetTypeListSchema(),
929
+ language_tags: languageTagListSchema(),
822
930
  min_reward: z.number().int().min(0).default(5_000),
823
931
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
824
932
  web3: z.boolean().default(false),
@@ -837,15 +945,15 @@ export function createBbradarServer(client, config) {
837
945
  output_mode: programListModeSchema.default("compact"),
838
946
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
839
947
  },
840
- outputSchema: findProgramsOutputShape,
948
+ outputSchema: findProgramsOutputShape(),
841
949
  annotations: readOnlyAnnotations
842
950
  }, (args) => runTool("find_reward_programs", config, rateLimiter, async () => findRewardPrograms(client, config, args)));
843
951
  server.registerTool("find_web3_contests", {
844
952
  title: "Find Web3 Contests",
845
953
  description: "Web3 contest or competitive audit shortlist.",
846
954
  inputSchema: {
847
- platforms: stringListSchema,
848
- tags: stringListSchema,
955
+ platforms: platformListSchema(),
956
+ tags: tagListSchema(),
849
957
  min_reward: z.number().int().min(0).optional(),
850
958
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
851
959
  require_contest_signal: z.boolean().default(true),
@@ -860,17 +968,17 @@ export function createBbradarServer(client, config) {
860
968
  output_mode: programListModeSchema.default("compact"),
861
969
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
862
970
  },
863
- outputSchema: findProgramsOutputShape,
971
+ outputSchema: findProgramsOutputShape(),
864
972
  annotations: readOnlyAnnotations
865
973
  }, (args) => runTool("find_web3_contests", config, rateLimiter, async () => findWeb3Contests(client, config, args)));
866
974
  server.registerTool("find_language_programs", {
867
975
  title: "Find Language Programs",
868
976
  description: "Language-specific program shortlist.",
869
977
  inputSchema: {
870
- platforms: stringListSchema,
871
- tags: stringListSchema,
872
- language_tags: z.array(filterValueSchema).min(1).max(50),
873
- target_types: stringListSchema,
978
+ platforms: platformListSchema(),
979
+ tags: tagListSchema(),
980
+ language_tags: z.array(filterValueSchema()).min(1).max(50),
981
+ target_types: targetTypeListSchema(),
874
982
  min_reward: z.number().int().min(0).optional(),
875
983
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
876
984
  require_bounty: z.boolean().default(false),
@@ -887,18 +995,18 @@ export function createBbradarServer(client, config) {
887
995
  output_mode: programListModeSchema.default("compact"),
888
996
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
889
997
  },
890
- outputSchema: findProgramsOutputShape,
998
+ outputSchema: findProgramsOutputShape(),
891
999
  annotations: readOnlyAnnotations
892
1000
  }, (args) => runTool("find_language_programs", config, rateLimiter, async () => findLanguagePrograms(client, config, args)));
893
1001
  server.registerTool("find_target_type_programs", {
894
1002
  title: "Find Target Type Programs",
895
1003
  description: "Target-type program shortlist.",
896
1004
  inputSchema: {
897
- platforms: stringListSchema,
898
- tags: stringListSchema,
899
- target_types: z.array(filterValueSchema).min(1).max(50),
900
- scope_tags: stringListSchema,
901
- language_tags: stringListSchema,
1005
+ platforms: platformListSchema(),
1006
+ tags: tagListSchema(),
1007
+ target_types: z.array(filterValueSchema()).min(1).max(50),
1008
+ scope_tags: scopeTagListSchema(),
1009
+ language_tags: languageTagListSchema(),
902
1010
  min_reward: z.number().int().min(0).optional(),
903
1011
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
904
1012
  require_bounty: z.boolean().default(false),
@@ -913,17 +1021,17 @@ export function createBbradarServer(client, config) {
913
1021
  output_mode: programListModeSchema.default("compact"),
914
1022
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
915
1023
  },
916
- outputSchema: findProgramsOutputShape,
1024
+ outputSchema: findProgramsOutputShape(),
917
1025
  annotations: readOnlyAnnotations
918
1026
  }, (args) => runTool("find_target_type_programs", config, rateLimiter, async () => findTargetTypePrograms(client, config, args)));
919
1027
  server.registerTool("find_vdp_programs", {
920
1028
  title: "Find VDP Programs",
921
1029
  description: "VDP/disclosure-only/no-bounty program shortlist.",
922
1030
  inputSchema: {
923
- platforms: stringListSchema,
924
- tags: stringListSchema,
925
- target_types: stringListSchema,
926
- language_tags: stringListSchema,
1031
+ platforms: platformListSchema(),
1032
+ tags: tagListSchema(),
1033
+ target_types: targetTypeListSchema(),
1034
+ language_tags: languageTagListSchema(),
927
1035
  vdp_mode: z.enum(["only_likely", "only_known_no_bounty"]).default("only_likely"),
928
1036
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
929
1037
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
@@ -933,17 +1041,17 @@ export function createBbradarServer(client, config) {
933
1041
  output_mode: programListModeSchema.default("compact"),
934
1042
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
935
1043
  },
936
- outputSchema: findProgramsOutputShape,
1044
+ outputSchema: findProgramsOutputShape(),
937
1045
  annotations: readOnlyAnnotations
938
1046
  }, (args) => runTool("find_vdp_programs", config, rateLimiter, async () => findVdpPrograms(client, config, args)));
939
1047
  server.registerTool("find_paid_programs", {
940
1048
  title: "Find Paid Programs",
941
1049
  description: "Paid bounty program shortlist.",
942
1050
  inputSchema: {
943
- platforms: stringListSchema,
944
- tags: stringListSchema,
945
- target_types: stringListSchema,
946
- language_tags: stringListSchema,
1051
+ platforms: platformListSchema(),
1052
+ tags: tagListSchema(),
1053
+ target_types: targetTypeListSchema(),
1054
+ language_tags: languageTagListSchema(),
947
1055
  min_reward: z.number().int().min(0).optional(),
948
1056
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
949
1057
  min_eligible_targets: z.number().int().min(0).default(1),
@@ -956,18 +1064,18 @@ export function createBbradarServer(client, config) {
956
1064
  output_mode: programListModeSchema.default("compact"),
957
1065
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
958
1066
  },
959
- outputSchema: findProgramsOutputShape,
1067
+ outputSchema: findProgramsOutputShape(),
960
1068
  annotations: readOnlyAnnotations
961
1069
  }, (args) => runTool("find_paid_programs", config, rateLimiter, async () => findPaidPrograms(client, config, args)));
962
1070
  server.registerTool("find_stack_matches", {
963
1071
  title: "Find Stack Matches",
964
1072
  description: "Combined stack/language/type/reward shortlist.",
965
1073
  inputSchema: {
966
- platforms: stringListSchema,
967
- tags: stringListSchema,
968
- language_tags: stringListSchema,
969
- target_types: stringListSchema,
970
- scope_tags: stringListSchema,
1074
+ platforms: platformListSchema(),
1075
+ tags: tagListSchema(),
1076
+ language_tags: languageTagListSchema(),
1077
+ target_types: targetTypeListSchema(),
1078
+ scope_tags: scopeTagListSchema(),
971
1079
  min_reward: z.number().int().min(0).optional(),
972
1080
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
973
1081
  web3: z.boolean().default(false),
@@ -986,17 +1094,17 @@ export function createBbradarServer(client, config) {
986
1094
  output_mode: programListModeSchema.default("compact"),
987
1095
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
988
1096
  },
989
- outputSchema: findProgramsOutputShape,
1097
+ outputSchema: findProgramsOutputShape(),
990
1098
  annotations: readOnlyAnnotations
991
1099
  }, (args) => runTool("find_stack_matches", config, rateLimiter, async () => findStackMatches(client, config, args)));
992
1100
  server.registerTool("find_low_noise_programs", {
993
1101
  title: "Find Low Noise Programs",
994
1102
  description: "Low-public-report program shortlist.",
995
1103
  inputSchema: {
996
- platforms: stringListSchema,
997
- tags: stringListSchema,
998
- target_types: stringListSchema,
999
- language_tags: stringListSchema,
1104
+ platforms: platformListSchema(),
1105
+ tags: tagListSchema(),
1106
+ target_types: targetTypeListSchema(),
1107
+ language_tags: languageTagListSchema(),
1000
1108
  min_reward: z.number().int().min(0).optional(),
1001
1109
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
1002
1110
  max_public_report_count: z.number().int().min(0).default(5),
@@ -1012,43 +1120,43 @@ export function createBbradarServer(client, config) {
1012
1120
  output_mode: programListModeSchema.default("compact"),
1013
1121
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1014
1122
  },
1015
- outputSchema: findProgramsOutputShape,
1123
+ outputSchema: findProgramsOutputShape(),
1016
1124
  annotations: readOnlyAnnotations
1017
1125
  }, (args) => runTool("find_low_noise_programs", config, rateLimiter, async () => findLowNoisePrograms(client, config, args)));
1018
1126
  server.registerTool("find_recent_by_type", {
1019
1127
  title: "Find Recent By Type",
1020
1128
  description: "Recent changes by target type grouped by program.",
1021
1129
  inputSchema: {
1022
- target_type: filterValueSchema,
1130
+ target_type: filterValueSchema(),
1023
1131
  change_type: z.enum(["added", "updated", "removed"]).default("added"),
1024
1132
  include_removed: z.boolean().default(false),
1025
1133
  include_out_of_scope: z.boolean().default(false),
1026
1134
  include_ineligible: z.boolean().default(false),
1027
1135
  search: searchTextSchema.optional(),
1028
- platforms: stringListSchema,
1029
- tags: stringListSchema,
1030
- language_tags: stringListSchema,
1136
+ platforms: platformListSchema(),
1137
+ tags: tagListSchema(),
1138
+ language_tags: languageTagListSchema(),
1031
1139
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
1032
1140
  max_programs: z.number().int().min(1).max(50).default(10),
1033
1141
  sample_size: z.number().int().min(1).max(20).default(5),
1034
1142
  target_list_mode: targetListModeSchema.default("identifiers")
1035
1143
  },
1036
1144
  outputSchema: {
1037
- ...apiEnvelopeOutputShape,
1038
- source_requests: z.array(jsonRecordSchema).optional(),
1039
- activity: z.array(jsonRecordSchema).optional(),
1040
- meta: jsonRecordSchema.optional()
1145
+ ...apiEnvelopeOutputShape(),
1146
+ source_requests: z.array(jsonRecordSchema()).optional(),
1147
+ activity: z.array(jsonRecordSchema()).optional(),
1148
+ meta: jsonRecordSchema().optional()
1041
1149
  },
1042
1150
  annotations: readOnlyAnnotations
1043
1151
  }, (args) => runTool("find_recent_by_type", config, rateLimiter, async () => findRecentByType(client, config, args)));
1044
1152
  server.registerTool("find_programs_by_target", {
1045
1153
  title: "Find Programs By Target",
1046
- description: "Reverse lookup target/domain ownership.",
1154
+ description: "Reverse lookup target/domain ownership. Accepts domains, URLs, wildcards, target names, and partial target text.",
1047
1155
  inputSchema: {
1048
- target_query: searchTextSchema,
1049
- match_mode: targetMatchModeSchema.default("contains"),
1050
- platforms: stringListSchema,
1051
- tags: stringListSchema,
1156
+ target_query: targetQuerySchema,
1157
+ match_mode: targetMatchModeSchema.default("smart"),
1158
+ platforms: platformListSchema(),
1159
+ tags: tagListSchema(),
1052
1160
  use_api_search: z.boolean().default(true),
1053
1161
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(2),
1054
1162
  max_results: z.number().int().min(1).max(25).default(10),
@@ -1059,33 +1167,33 @@ export function createBbradarServer(client, config) {
1059
1167
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
1060
1168
  },
1061
1169
  outputSchema: {
1062
- ...findProgramsOutputShape,
1063
- matches: z.array(jsonRecordSchema).optional()
1170
+ ...findProgramsOutputShape(),
1171
+ matches: z.array(jsonRecordSchema()).optional()
1064
1172
  },
1065
1173
  annotations: readOnlyAnnotations
1066
1174
  }, (args) => runTool("find_programs_by_target", config, rateLimiter, async () => findProgramsByTarget(client, config, args)));
1067
1175
  server.registerTool("find_program_candidates", {
1068
1176
  title: "Find BBRadar Program Candidates",
1069
- description: "Default ranked BBRadar discovery preset.",
1177
+ description: "Start here for broad program discovery. Default ranked hunt-candidate preset with safe filters and compact output.",
1070
1178
  inputSchema: programCandidatePresetInputSchema,
1071
- outputSchema: findProgramsOutputShape,
1179
+ outputSchema: findProgramsOutputShape(),
1072
1180
  annotations: readOnlyAnnotations
1073
1181
  }, (args) => runTool("find_program_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
1074
1182
  server.registerTool("find_hunt_candidates", {
1075
1183
  title: "Find BBRadar Hunt Candidates",
1076
- description: "Legacy alias for find_program_candidates.",
1184
+ description: "Legacy alias for find_program_candidates. Prefer find_program_candidates for new calls.",
1077
1185
  inputSchema: programCandidatePresetInputSchema,
1078
- outputSchema: findProgramsOutputShape,
1186
+ outputSchema: findProgramsOutputShape(),
1079
1187
  annotations: readOnlyAnnotations
1080
1188
  }, (args) => runTool("find_hunt_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
1081
1189
  server.registerTool("find_fresh_hunt_candidates", {
1082
1190
  title: "Find Fresh Hunt Candidates",
1083
1191
  description: "Freshness-first hunt shortlist.",
1084
1192
  inputSchema: {
1085
- platforms: stringListSchema,
1086
- tags: stringListSchema,
1087
- target_types: z.array(filterValueSchema).max(50).default(["domain"]),
1088
- language_tags: stringListSchema,
1193
+ platforms: platformListSchema(),
1194
+ tags: tagListSchema(),
1195
+ target_types: z.array(filterValueSchema()).max(50).default(["domain"]),
1196
+ language_tags: languageTagListSchema(),
1089
1197
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default(["elite", "hot", "strong", "potential"]),
1090
1198
  min_added_7d: z.number().int().min(0).default(1),
1091
1199
  max_public_report_count: z.number().int().min(0).default(25),
@@ -1107,7 +1215,7 @@ export function createBbradarServer(client, config) {
1107
1215
  output_mode: programListModeSchema.default("compact"),
1108
1216
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
1109
1217
  },
1110
- outputSchema: findProgramsOutputShape,
1218
+ outputSchema: findProgramsOutputShape(),
1111
1219
  annotations: readOnlyAnnotations
1112
1220
  }, (args) => runTool("find_fresh_hunt_candidates", config, rateLimiter, async () => findPrograms(client, config, {
1113
1221
  platforms: args.platforms,
@@ -1151,10 +1259,10 @@ export function createBbradarServer(client, config) {
1151
1259
  title: "Find Recently Updated Programs",
1152
1260
  description: "Recently updated program shortlist across all platforms by default. Platform filters are applied only when explicitly provided.",
1153
1261
  inputSchema: {
1154
- platforms: stringListSchema,
1155
- tags: stringListSchema,
1156
- target_types: stringListSchema,
1157
- language_tags: stringListSchema,
1262
+ platforms: platformListSchema(),
1263
+ tags: tagListSchema(),
1264
+ target_types: targetTypeListSchema(),
1265
+ language_tags: languageTagListSchema(),
1158
1266
  web3: z.boolean().default(false),
1159
1267
  updated_since: isoDateTimeSchema.optional(),
1160
1268
  require_bounty: z.boolean().default(false),
@@ -1169,35 +1277,50 @@ export function createBbradarServer(client, config) {
1169
1277
  output_mode: programListModeSchema.default("compact"),
1170
1278
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1171
1279
  },
1172
- outputSchema: findProgramsOutputShape,
1280
+ outputSchema: findProgramsOutputShape(),
1173
1281
  annotations: readOnlyAnnotations
1174
1282
  }, (args) => runTool("find_recently_updated_programs", config, rateLimiter, async () => findRecentlyUpdatedPrograms(client, config, args)));
1175
1283
  server.registerTool("list_filters", {
1176
1284
  title: "List BBRadar Filters",
1177
1285
  description: "Discover observed filter values.",
1178
1286
  inputSchema: {
1179
- platforms: stringListSchema,
1180
- tags: stringListSchema,
1287
+ platforms: platformListSchema(),
1288
+ tags: tagListSchema(),
1181
1289
  max_pages: z.number().int().min(1).max(MAX_FILTER_DISCOVERY_PAGES).default(DEFAULT_FILTER_DISCOVERY_PAGES),
1182
1290
  include_target_facets: z.boolean().default(true),
1183
1291
  target_sample_programs: z.number().int().min(0).max(MAX_FILTER_TARGET_SAMPLE_PROGRAMS).default(DEFAULT_FILTER_TARGET_SAMPLE_PROGRAMS),
1184
1292
  upstream_request_budget: z.number().int().min(1).max(MAX_FIND_UPSTREAM_REQUEST_BUDGET).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1185
1293
  },
1186
1294
  outputSchema: {
1187
- ...apiEnvelopeOutputShape,
1188
- filters: jsonRecordSchema.optional(),
1189
- observed_from: jsonRecordSchema.optional(),
1190
- upstream_budget: jsonRecordSchema.optional()
1295
+ ...apiEnvelopeOutputShape(),
1296
+ filters: jsonRecordSchema().optional(),
1297
+ observed_from: jsonRecordSchema().optional(),
1298
+ upstream_budget: jsonRecordSchema().optional()
1191
1299
  },
1192
1300
  annotations: readOnlyAnnotations
1193
1301
  }, (args) => runTool("list_filters", config, rateLimiter, async () => listFilters(client, args)));
1302
+ server.registerTool("get_bbradar_guide", {
1303
+ title: "Get BBRadar Tool Guide",
1304
+ description: "Local quick-start guide for choosing BBRadar tools. Call this when the user's request is ambiguous or when a smaller model may need a decision tree.",
1305
+ inputSchema: {
1306
+ topic: guideTopicSchema.describe("Guide focus area. Use start for the compact default decision tree.")
1307
+ },
1308
+ outputSchema: {
1309
+ ...apiEnvelopeOutputShape(),
1310
+ guide: jsonRecordSchema().optional()
1311
+ },
1312
+ annotations: readOnlyAnnotations
1313
+ }, (args) => runTool("get_bbradar_guide", config, rateLimiter, async () => ({
1314
+ request_id: randomUUID(),
1315
+ guide: bbradarGuide(args.topic)
1316
+ })));
1194
1317
  server.registerTool("get_mcp_status", {
1195
1318
  title: "Get BBRadar MCP Status",
1196
1319
  description: "Local MCP status and timing metrics.",
1197
1320
  inputSchema: {},
1198
1321
  outputSchema: {
1199
- ...apiEnvelopeOutputShape,
1200
- status: jsonRecordSchema.optional()
1322
+ ...apiEnvelopeOutputShape(),
1323
+ status: jsonRecordSchema().optional()
1201
1324
  },
1202
1325
  annotations: readOnlyAnnotations
1203
1326
  }, () => runTool("get_mcp_status", config, rateLimiter, async () => ({
@@ -1226,41 +1349,41 @@ export function createBbradarServer(client, config) {
1226
1349
  title: "Compare BBRadar Programs",
1227
1350
  description: "Compare programs.",
1228
1351
  inputSchema: {
1229
- program_ids: z.array(programIdSchema).min(2).max(10),
1352
+ program_ids: z.array(programIdentifierSchema).min(2).max(10),
1230
1353
  include_target_samples: z.boolean().default(false),
1231
1354
  target_sample_size: z.number().int().min(1).max(MAX_FIND_TARGET_SAMPLES).default(5),
1232
1355
  only_in_scope_targets: z.boolean().default(true),
1233
1356
  only_bounty_eligible_targets: z.boolean().default(true),
1234
1357
  output_mode: programListModeSchema.default("compact")
1235
1358
  },
1236
- outputSchema: decisionOutputShape,
1359
+ outputSchema: decisionOutputShape(),
1237
1360
  annotations: readOnlyAnnotations
1238
1361
  }, (args) => runTool("compare_programs", config, rateLimiter, async () => comparePrograms(client, config, args)));
1239
1362
  server.registerTool("compare_programs_compact", {
1240
1363
  title: "Compare Programs Compact",
1241
1364
  description: "Compact program comparison.",
1242
1365
  inputSchema: {
1243
- program_ids: z.array(programIdSchema).min(2).max(10),
1366
+ program_ids: z.array(programIdentifierSchema).min(2).max(10),
1244
1367
  include_target_samples: z.boolean().default(false),
1245
1368
  target_sample_size: z.number().int().min(1).max(5).default(3),
1246
1369
  only_in_scope_targets: z.boolean().default(true),
1247
1370
  only_bounty_eligible_targets: z.boolean().default(true)
1248
1371
  },
1249
- outputSchema: decisionOutputShape,
1372
+ outputSchema: decisionOutputShape(),
1250
1373
  annotations: readOnlyAnnotations
1251
1374
  }, (args) => runTool("compare_programs_compact", config, rateLimiter, async () => comparePrograms(client, config, { ...args, output_mode: "compact" })));
1252
1375
  server.registerTool("summarize_program_activity", {
1253
1376
  title: "Summarize Program Activity",
1254
1377
  description: "Summarize one program's activity.",
1255
1378
  inputSchema: {
1256
- program_id: programIdSchema,
1379
+ program_id: programIdentifierSchema,
1257
1380
  recent_changes_limit: z.number().int().min(0).max(MAX_RECENT_CHANGES).default(25)
1258
1381
  },
1259
1382
  outputSchema: {
1260
- ...apiEnvelopeOutputShape,
1261
- program: programOutputSchema.optional(),
1262
- activity: jsonRecordSchema.optional(),
1263
- changes: z.array(jsonRecordSchema).optional()
1383
+ ...apiEnvelopeOutputShape(),
1384
+ program: programOutputSchema().optional(),
1385
+ activity: jsonRecordSchema().optional(),
1386
+ changes: z.array(jsonRecordSchema()).optional()
1264
1387
  },
1265
1388
  annotations: readOnlyAnnotations
1266
1389
  }, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => summarizeProgramActivity(client, config, programId, args.recent_changes_limit))));
@@ -1268,7 +1391,7 @@ export function createBbradarServer(client, config) {
1268
1391
  title: "Get Program Brief",
1269
1392
  description: "Compact worth-hunting brief for one program.",
1270
1393
  inputSchema: {
1271
- program_id: programIdSchema,
1394
+ program_id: programIdentifierSchema,
1272
1395
  target_sample_size: z.number().int().min(1).max(50).default(10),
1273
1396
  target_list_mode: targetListModeSchema.default("compact"),
1274
1397
  include_recent_changes: z.boolean().default(true),
@@ -1277,13 +1400,13 @@ export function createBbradarServer(client, config) {
1277
1400
  include_ineligible: z.boolean().default(false)
1278
1401
  },
1279
1402
  outputSchema: {
1280
- ...apiEnvelopeOutputShape,
1281
- source_requests: z.array(jsonRecordSchema).optional(),
1282
- program: programOutputSchema.optional(),
1283
- brief: jsonRecordSchema.optional(),
1284
- target_samples: jsonRecordSchema.optional(),
1285
- recent_changes: z.array(jsonRecordSchema).optional(),
1286
- meta: jsonRecordSchema.optional()
1403
+ ...apiEnvelopeOutputShape(),
1404
+ source_requests: z.array(jsonRecordSchema()).optional(),
1405
+ program: programOutputSchema().optional(),
1406
+ brief: jsonRecordSchema().optional(),
1407
+ target_samples: jsonRecordSchema().optional(),
1408
+ recent_changes: z.array(jsonRecordSchema()).optional(),
1409
+ meta: jsonRecordSchema().optional()
1287
1410
  },
1288
1411
  annotations: readOnlyAnnotations
1289
1412
  }, (args) => runTool("get_program_brief", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramBrief(client, config, { ...args, program_id: programId }))));
@@ -1291,8 +1414,8 @@ export function createBbradarServer(client, config) {
1291
1414
  title: "Find Recently Added Wildcards",
1292
1415
  description: "Recently added wildcard programs.",
1293
1416
  inputSchema: {
1294
- platforms: stringListSchema,
1295
- tags: stringListSchema,
1417
+ platforms: platformListSchema(),
1418
+ tags: tagListSchema(),
1296
1419
  min_added_7d: z.number().int().min(1).default(1),
1297
1420
  max_public_report_count: z.number().int().min(0).optional(),
1298
1421
  require_bounty: z.boolean().default(true),
@@ -1300,7 +1423,7 @@ export function createBbradarServer(client, config) {
1300
1423
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
1301
1424
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1302
1425
  },
1303
- outputSchema: findProgramsOutputShape,
1426
+ outputSchema: findProgramsOutputShape(),
1304
1427
  annotations: readOnlyAnnotations
1305
1428
  }, (args) => runTool("find_recently_added_wildcards", config, rateLimiter, async () => findPrograms(client, config, {
1306
1429
  platforms: args.platforms,
@@ -1343,10 +1466,10 @@ export function createBbradarServer(client, config) {
1343
1466
  title: "Find Low Competition High Reward",
1344
1467
  description: "Legacy low-report high-reward shortlist.",
1345
1468
  inputSchema: {
1346
- platforms: stringListSchema,
1347
- tags: stringListSchema,
1348
- target_types: stringListSchema,
1349
- language_tags: stringListSchema,
1469
+ platforms: platformListSchema(),
1470
+ tags: tagListSchema(),
1471
+ target_types: targetTypeListSchema(),
1472
+ language_tags: languageTagListSchema(),
1350
1473
  max_public_report_count: z.number().int().min(0).default(10),
1351
1474
  min_bounty_max: z.number().int().min(0).default(5_000),
1352
1475
  min_eligible_targets: z.number().int().min(0).default(1),
@@ -1359,7 +1482,7 @@ export function createBbradarServer(client, config) {
1359
1482
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
1360
1483
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1361
1484
  },
1362
- outputSchema: findProgramsOutputShape,
1485
+ outputSchema: findProgramsOutputShape(),
1363
1486
  annotations: readOnlyAnnotations
1364
1487
  }, (args) => runTool("find_low_competition_high_reward", config, rateLimiter, async () => findPrograms(client, config, {
1365
1488
  platforms: args.platforms,
@@ -1402,17 +1525,17 @@ export function createBbradarServer(client, config) {
1402
1525
  title: "Get Program Delta",
1403
1526
  description: "Recent target changes for one program.",
1404
1527
  inputSchema: {
1405
- program_id: programIdSchema,
1528
+ program_id: programIdentifierSchema,
1406
1529
  include_removed: z.boolean().default(true),
1407
1530
  include_ineligible: z.boolean().default(false),
1408
1531
  include_out_of_scope: z.boolean().default(false),
1409
1532
  page_size: recentChangesPageSizeSchema.default(50)
1410
1533
  },
1411
1534
  outputSchema: {
1412
- ...apiEnvelopeOutputShape,
1413
- program: programOutputSchema.optional(),
1414
- changes: z.array(jsonRecordSchema).optional(),
1415
- meta: jsonRecordSchema.optional()
1535
+ ...apiEnvelopeOutputShape(),
1536
+ program: programOutputSchema().optional(),
1537
+ changes: z.array(jsonRecordSchema()).optional(),
1538
+ meta: jsonRecordSchema().optional()
1416
1539
  },
1417
1540
  annotations: readOnlyAnnotations
1418
1541
  }, (args) => runTool("get_program_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramDelta(client, config, { ...args, program_id: programId }))));
@@ -1420,9 +1543,9 @@ export function createBbradarServer(client, config) {
1420
1543
  title: "Export BBRadar Targets",
1421
1544
  description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
1422
1545
  inputSchema: {
1423
- program_ids: z.array(programIdSchema).max(50).default([]),
1424
- platforms: stringListSchema,
1425
- tags: stringListSchema,
1546
+ program_ids: z.array(programIdentifierSchema).max(50).default([]),
1547
+ platforms: platformListSchema(),
1548
+ tags: tagListSchema(),
1426
1549
  opportunity_levels: z.array(z.enum(["elite", "hot", "strong", "potential"])).max(4).default([]),
1427
1550
  include_out_of_scope: z.boolean().default(false),
1428
1551
  include_ineligible: z.boolean().default(false),
@@ -1430,19 +1553,26 @@ export function createBbradarServer(client, config) {
1430
1553
  limit: z.number().int().min(1).max(MAX_TARGET_EXPORT).default(MAX_TARGET_EXPORT)
1431
1554
  },
1432
1555
  outputSchema: {
1433
- ...apiEnvelopeOutputShape,
1556
+ ...apiEnvelopeOutputShape(),
1434
1557
  export: z.unknown().optional(),
1435
- meta: jsonRecordSchema.optional()
1558
+ meta: jsonRecordSchema().optional()
1436
1559
  },
1437
1560
  annotations: readOnlyAnnotations
1438
1561
  }, (args) => runTool("export_targets", config, rateLimiter, async () => {
1562
+ const resolvedProgramIds = await resolveProgramIdentifiers(client, config, args.program_ids);
1563
+ const exportArgs = {
1564
+ ...args,
1565
+ program_ids: resolvedProgramIds.entries.map((entry) => entry.resolvedProgramId)
1566
+ };
1439
1567
  try {
1440
- const api = await client.exportTargets(compactRecord(args));
1568
+ const api = await client.exportTargets(compactRecord(exportArgs));
1441
1569
  const exportId = randomUUID();
1442
1570
  const sanitizedExport = sanitizeTargetsForExport(api.data, args.limit);
1443
1571
  const resourceUri = exportResourceUri(exportId);
1444
1572
  rememberExport(exportStore, exportId, sanitizedExport);
1445
1573
  return withApiMetadata(api, {
1574
+ source_requests: resolvedProgramIds.sourceRequests.length > 0 ? resolvedProgramIds.sourceRequests : undefined,
1575
+ warnings: resolvedProgramIds.warnings.length > 0 ? resolvedProgramIds.warnings : undefined,
1446
1576
  export: {
1447
1577
  export_id: exportId,
1448
1578
  resource_uri: resourceUri,
@@ -1456,13 +1586,168 @@ export function createBbradarServer(client, config) {
1456
1586
  if (!(error instanceof BBRadarApiError) || (error.status !== 404 && error.status !== 405)) {
1457
1587
  throw error;
1458
1588
  }
1459
- return exportTargetsFromProgramTargetsFallback(client, config, exportStore, args, error);
1589
+ const fallbackPayload = await exportTargetsFromProgramTargetsFallback(client, config, exportStore, exportArgs, error);
1590
+ const fallbackSourceRequests = readArray(fallbackPayload.source_requests).filter((request) => readObject(request) !== undefined);
1591
+ const fallbackWarnings = readArray(fallbackPayload.warnings).filter((warning) => typeof warning === "string");
1592
+ return stripUndefined({
1593
+ ...fallbackPayload,
1594
+ source_requests: [...resolvedProgramIds.sourceRequests, ...fallbackSourceRequests],
1595
+ warnings: uniqueStrings([...resolvedProgramIds.warnings, ...fallbackWarnings])
1596
+ });
1460
1597
  }
1461
1598
  }));
1462
1599
  registerPrompts(server);
1463
1600
  registerResources(server, client, config, exportStore);
1464
1601
  return server;
1465
1602
  }
1603
+ function bbradarGuide(topic) {
1604
+ const primaryEntryPoints = [
1605
+ {
1606
+ tool: "find_program_candidates",
1607
+ use_when: "The user wants good programs to hunt, broad discovery, or does not name one exact program.",
1608
+ minimum_arguments: {},
1609
+ useful_arguments: ["platforms", "target_types", "language_tags", "min_bounty_max", "max_public_report_count", "fresh_launch_days", "has_api_scope"]
1610
+ },
1611
+ {
1612
+ tool: "run_program_name_action",
1613
+ use_when: "The user names one program and asks for a brief, targets, new targets, scope delta, or target breakdown.",
1614
+ minimum_arguments: { query: "program name or handle", action: "brief" },
1615
+ action_values: ["brief", "targets", "new_targets", "scope_delta", "target_breakdown"]
1616
+ },
1617
+ {
1618
+ tool: "get_program_eligible_targets_by_name",
1619
+ use_when: "The user names one program and asks for in-scope bounty-eligible targets.",
1620
+ minimum_arguments: { query: "program name or handle" },
1621
+ useful_arguments: ["target_list_mode", "limit", "strict_scope_filter"]
1622
+ },
1623
+ {
1624
+ tool: "get_bbradar_guide",
1625
+ use_when: "The tool choice is unclear.",
1626
+ minimum_arguments: { topic: "start" }
1627
+ }
1628
+ ];
1629
+ const decisionTree = [
1630
+ {
1631
+ user_goal: "find programs to hunt",
1632
+ first_tool: "find_program_candidates",
1633
+ then: "Use get_program_brief on the best program_id if the user wants details."
1634
+ },
1635
+ {
1636
+ user_goal: "named program summary",
1637
+ first_tool: "run_program_name_action",
1638
+ arguments: { query: "program name", action: "brief" }
1639
+ },
1640
+ {
1641
+ user_goal: "named program targets",
1642
+ first_tool: "get_program_eligible_targets_by_name",
1643
+ arguments: { query: "program name", target_list_mode: "identifiers" }
1644
+ },
1645
+ {
1646
+ user_goal: "find which program owns a target",
1647
+ first_tool: "find_programs_by_target",
1648
+ arguments: { target_query: "domain, URL, wildcard, package/repo name, or short target phrase", match_mode: "smart" }
1649
+ },
1650
+ {
1651
+ user_goal: "fresh or recently changed scope",
1652
+ first_tool: "find_recently_updated_programs",
1653
+ then: "Use get_latest_added_targets for global added targets, or check_program_new_targets when the user names one program."
1654
+ },
1655
+ {
1656
+ user_goal: "compare a short list",
1657
+ first_tool: "compare_programs_compact",
1658
+ arguments: { program_ids: ["Platform:handle", "Platform:handle"] }
1659
+ },
1660
+ {
1661
+ user_goal: "export targets",
1662
+ first_tool: "export_targets",
1663
+ then: "Use the returned bbradar://exports resource_uri for the full local export payload."
1664
+ },
1665
+ {
1666
+ user_goal: "unknown filter values",
1667
+ first_tool: "list_filters",
1668
+ then: "Reuse observed platforms, scope tags, target types, and language tags in discovery tools."
1669
+ }
1670
+ ];
1671
+ const advancedTools = {
1672
+ discovery: ["find_stack_matches", "find_language_programs", "find_target_type_programs", "find_reward_programs", "find_paid_programs", "find_web3_contests", "find_low_noise_programs"],
1673
+ freshness: ["find_recently_updated_programs", "find_fresh_hunt_candidates", "get_latest_added_targets", "get_recent_target_activity", "get_program_scope_delta", "check_watchlist_new_targets"],
1674
+ details: ["get_program_brief", "get_program_scope_summary", "get_program_target_breakdown", "find_programs_by_target"],
1675
+ exports: ["export_targets"]
1676
+ };
1677
+ const filters = {
1678
+ platforms: "Only set when the user explicitly names a platform. Do not infer Immunefi, HackerOne, etc.",
1679
+ tags: "Broad combined tag filter across scope, language, and target-type signals.",
1680
+ scope_tags: "Scope category signals such as domain, api, mobile, smart-contract, wildcard.",
1681
+ target_types: "Target-kind signals such as domain, api, url, mobile, smart-contract.",
1682
+ language_tags: "Technology/language signals such as javascript, solidity, web3, php, python.",
1683
+ vdp_mode: "Default exclude. Include VDP/no-bounty data only when asked."
1684
+ };
1685
+ const examples = [
1686
+ {
1687
+ user_says: "best low-noise API programs",
1688
+ call: { tool: "find_program_candidates", arguments: { has_api_scope: true, max_public_report_count: 5, target_types: ["api"] } }
1689
+ },
1690
+ {
1691
+ user_says: "targets for Nado",
1692
+ call: { tool: "get_program_eligible_targets_by_name", arguments: { query: "Nado", target_list_mode: "identifiers" } }
1693
+ },
1694
+ {
1695
+ user_says: "what changed on Aztec",
1696
+ call: { tool: "run_program_name_action", arguments: { query: "Aztec", action: "scope_delta" } }
1697
+ },
1698
+ {
1699
+ user_says: "fresh solidity programs",
1700
+ call: { tool: "find_fresh_hunt_candidates", arguments: { language_tags: ["solidity"], target_types: ["smart-contract"] } }
1701
+ },
1702
+ {
1703
+ user_says: "who owns https://api.example.com/v1/users",
1704
+ call: { tool: "find_programs_by_target", arguments: { target_query: "https://api.example.com/v1/users" } }
1705
+ }
1706
+ ];
1707
+ return {
1708
+ topic,
1709
+ rule_zero: "BBRadar is passive intelligence only. Never scan or contact targets.",
1710
+ defaults: {
1711
+ output_mode: "compact",
1712
+ target_list_mode: "identifiers",
1713
+ target_match_mode: "smart",
1714
+ include_out_of_scope: false,
1715
+ include_ineligible: false,
1716
+ require_bounty: true
1717
+ },
1718
+ primary_entry_points: topic === "start" ? primaryEntryPoints : primaryEntryPoints.filter((entry) => entry.tool !== "get_bbradar_guide"),
1719
+ decision_tree: decisionTree.filter((entry) => topic === "start" || guideEntryMatchesTopic(topic, entry)),
1720
+ filters,
1721
+ advanced_tools: advancedTools,
1722
+ examples
1723
+ };
1724
+ }
1725
+ function guideEntryMatchesTopic(topic, entry) {
1726
+ const goal = typeof entry.user_goal === "string" ? entry.user_goal : "";
1727
+ const firstTool = typeof entry.first_tool === "string" ? entry.first_tool : "";
1728
+ if (topic === "program") {
1729
+ return goal.includes("named program") || firstTool === "get_program_brief";
1730
+ }
1731
+ if (topic === "discovery") {
1732
+ return goal.includes("find programs");
1733
+ }
1734
+ if (topic === "freshness") {
1735
+ return goal.includes("fresh") || goal.includes("changed");
1736
+ }
1737
+ if (topic === "targets") {
1738
+ return goal.includes("target") || firstTool === "find_programs_by_target";
1739
+ }
1740
+ if (topic === "compare") {
1741
+ return goal.includes("compare");
1742
+ }
1743
+ if (topic === "export") {
1744
+ return goal.includes("export");
1745
+ }
1746
+ if (topic === "filters") {
1747
+ return goal.includes("filter");
1748
+ }
1749
+ return true;
1750
+ }
1466
1751
  function registerResources(server, client, config, exportStore) {
1467
1752
  server.registerResource("target-export", new ResourceTemplate("bbradar://exports{?export_id}", { list: undefined }), {
1468
1753
  title: "BBRadar Target Export",
@@ -1568,6 +1853,20 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
1568
1853
  fallback: cachedFallback
1569
1854
  };
1570
1855
  }
1856
+ if (!isStrictProgramId(requestedProgramId)) {
1857
+ const fallback = await resolveProgramNameIdentifier(client, config, requestedProgramId, "compact");
1858
+ if (fallback) {
1859
+ rememberProgramFallback(config, fallback);
1860
+ return {
1861
+ api: await client.getProgram(fallback.resolvedProgramId),
1862
+ programId: fallback.resolvedProgramId,
1863
+ sourceRequests: fallback.sourceRequests,
1864
+ warnings: fallback.warnings,
1865
+ fallback
1866
+ };
1867
+ }
1868
+ throw unresolvedProgramNameError(requestedProgramId);
1869
+ }
1571
1870
  try {
1572
1871
  return {
1573
1872
  api: await client.getProgram(requestedProgramId),
@@ -1619,6 +1918,39 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
1619
1918
  return withProgramIndexFallbackMetadata(payload, indexFallback);
1620
1919
  }
1621
1920
  }
1921
+ if (!isStrictProgramId(requestedProgramId)) {
1922
+ const fallback = await resolveProgramNameIdentifier(client, config, requestedProgramId, indexFallbackCallback ? "full" : "compact");
1923
+ if (fallback) {
1924
+ try {
1925
+ rememberProgramFallback(config, fallback);
1926
+ const payload = await callback(fallback.resolvedProgramId);
1927
+ return withProgramIdFallbackMetadata(payload, fallback);
1928
+ }
1929
+ catch (fallbackError) {
1930
+ if (!isProgramNotFoundError(fallbackError) || !indexFallbackCallback) {
1931
+ throw fallbackError;
1932
+ }
1933
+ const indexFallback = createProgramIndexFallbackFromStaleResolution(fallback, requestedProgramId, fallbackError) ??
1934
+ (await resolveIndexedProgramId(client, config, fallback.resolvedProgramId, fallbackError));
1935
+ if (!indexFallback) {
1936
+ throw fallbackError;
1937
+ }
1938
+ const combinedFallback = indexFallback.requestedProgramId === requestedProgramId
1939
+ ? indexFallback
1940
+ : {
1941
+ ...indexFallback,
1942
+ requestedProgramId,
1943
+ reason: fallback.reason,
1944
+ sourceRequests: [...fallback.sourceRequests, ...indexFallback.sourceRequests],
1945
+ warnings: uniqueStrings([...fallback.warnings, ...indexFallback.warnings])
1946
+ };
1947
+ rememberProgramFallback(config, combinedFallback);
1948
+ const payload = await indexFallbackCallback(combinedFallback);
1949
+ return withProgramIndexFallbackMetadata(payload, combinedFallback);
1950
+ }
1951
+ }
1952
+ throw unresolvedProgramNameError(requestedProgramId);
1953
+ }
1622
1954
  try {
1623
1955
  return await callback(requestedProgramId);
1624
1956
  }
@@ -1776,6 +2108,7 @@ async function resolveIndexedProgramId(client, config, requestedProgramId, stale
1776
2108
  requestedProgramId,
1777
2109
  resolvedProgramId: match.resolvedProgramId,
1778
2110
  query,
2111
+ reason: "stale_program_id",
1779
2112
  staleRequestId: staleApiError?.requestId,
1780
2113
  staleUpstreamRequestId: staleApiError?.upstreamRequestId,
1781
2114
  sourceRequests,
@@ -1784,6 +2117,37 @@ async function resolveIndexedProgramId(client, config, requestedProgramId, stale
1784
2117
  program: match.program
1785
2118
  };
1786
2119
  }
2120
+ async function resolveProgramNameIdentifier(client, config, requestedProgramName, outputMode) {
2121
+ const query = programSearchText(requestedProgramName);
2122
+ if (query.length < 2) {
2123
+ return undefined;
2124
+ }
2125
+ const resolution = await resolveProgram(client, config, {
2126
+ query,
2127
+ platforms: [],
2128
+ tags: [],
2129
+ opportunity_levels: [],
2130
+ max_pages: STALE_PROGRAM_ID_RESOLVE_PAGES,
2131
+ max_results: 5,
2132
+ output_mode: outputMode,
2133
+ upstream_request_budget: STALE_PROGRAM_ID_RESOLVE_BUDGET
2134
+ });
2135
+ const match = selectProgramIdFallbackMatch(requestedProgramName, resolution);
2136
+ if (!match) {
2137
+ return undefined;
2138
+ }
2139
+ const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
2140
+ const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
2141
+ return {
2142
+ requestedProgramId: requestedProgramName,
2143
+ resolvedProgramId: match.resolvedProgramId,
2144
+ query,
2145
+ reason: "program_name",
2146
+ sourceRequests,
2147
+ warnings: uniqueStrings(warnings),
2148
+ resolution
2149
+ };
2150
+ }
1787
2151
  async function resolveStaleProgramId(client, config, requestedProgramId, staleError) {
1788
2152
  const query = programSearchText(requestedProgramId);
1789
2153
  if (query.length < 2) {
@@ -1811,6 +2175,7 @@ async function resolveStaleProgramId(client, config, requestedProgramId, staleEr
1811
2175
  requestedProgramId,
1812
2176
  resolvedProgramId: match.resolvedProgramId,
1813
2177
  query,
2178
+ reason: "stale_program_id",
1814
2179
  staleRequestId: staleApiError?.requestId,
1815
2180
  staleUpstreamRequestId: staleApiError?.upstreamRequestId,
1816
2181
  sourceRequests,
@@ -1818,6 +2183,32 @@ async function resolveStaleProgramId(client, config, requestedProgramId, staleEr
1818
2183
  resolution
1819
2184
  };
1820
2185
  }
2186
+ async function resolveProgramIdentifiers(client, config, identifiers) {
2187
+ const entries = [];
2188
+ const sourceRequests = [];
2189
+ const warnings = [];
2190
+ for (const identifier of identifiers) {
2191
+ if (isStrictProgramId(identifier)) {
2192
+ entries.push({ requestedProgramId: identifier, resolvedProgramId: identifier, resolved: false });
2193
+ continue;
2194
+ }
2195
+ const fallback = await resolveProgramNameIdentifier(client, config, identifier, "compact");
2196
+ if (!fallback) {
2197
+ entries.push({ requestedProgramId: identifier, resolvedProgramId: identifier, resolved: false });
2198
+ warnings.push(`Could not resolve program name ${identifier}; used it as provided.`);
2199
+ continue;
2200
+ }
2201
+ entries.push({ requestedProgramId: identifier, resolvedProgramId: fallback.resolvedProgramId, resolved: true });
2202
+ sourceRequests.push(...fallback.sourceRequests);
2203
+ warnings.push(...fallback.warnings);
2204
+ rememberProgramFallback(config, fallback);
2205
+ }
2206
+ return {
2207
+ entries,
2208
+ sourceRequests,
2209
+ warnings: uniqueStrings(warnings)
2210
+ };
2211
+ }
1821
2212
  function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
1822
2213
  const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
1823
2214
  const requestedSearchText = normalizeTag(programSearchText(requestedProgramId));
@@ -1829,6 +2220,7 @@ function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
1829
2220
  const program = readObject(entry.program);
1830
2221
  const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
1831
2222
  const candidateHandle = normalizeTag(stringField(program, "handle") ?? "");
2223
+ const candidateName = normalizeTag(stringField(program, "name") ?? "");
1832
2224
  const candidateSearchText = resolvedProgramId ? normalizeTag(programSearchText(resolvedProgramId)) : "";
1833
2225
  const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
1834
2226
  const score = readNumber(entry.score) ?? 0;
@@ -1842,16 +2234,24 @@ function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
1842
2234
  candidateHandle.startsWith(`${requestedHandle}-`) ||
1843
2235
  candidateSearchText.startsWith(`${requestedSearchText}_`) ||
1844
2236
  candidateSearchText.startsWith(`${requestedSearchText}-`));
2237
+ const looseNameMatch = requestedPlatform.length === 0 &&
2238
+ score >= 85 &&
2239
+ (candidateHandle === requestedHandle ||
2240
+ candidateSearchText === requestedSearchText ||
2241
+ candidateName === requestedSearchText ||
2242
+ candidateName.includes(requestedSearchText) ||
2243
+ (candidateHandle.length > 0 && requestedSearchText.includes(candidateHandle)));
1845
2244
  return {
1846
2245
  resolvedProgramId,
1847
2246
  score,
1848
2247
  exactHandleMatch,
1849
- partialHandleMatch
2248
+ partialHandleMatch,
2249
+ looseNameMatch
1850
2250
  };
1851
2251
  })
1852
2252
  .filter((entry) => entry.resolvedProgramId !== undefined &&
1853
2253
  normalizeTag(entry.resolvedProgramId) !== normalizeTag(requestedProgramId) &&
1854
- (entry.exactHandleMatch || entry.partialHandleMatch) &&
2254
+ (entry.exactHandleMatch || entry.partialHandleMatch || entry.looseNameMatch) &&
1855
2255
  entry.score >= 80)
1856
2256
  .sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
1857
2257
  if (candidates.length === 0) {
@@ -1876,6 +2276,7 @@ function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
1876
2276
  const program = readObject(entry.program);
1877
2277
  const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
1878
2278
  const candidateHandle = normalizeTag(stringField(program, "handle") ?? (resolvedProgramId ? programHandleText(resolvedProgramId) : ""));
2279
+ const candidateName = normalizeTag(stringField(program, "name") ?? "");
1879
2280
  const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
1880
2281
  const score = readNumber(entry.score) ?? 0;
1881
2282
  const idMatch = resolvedProgramId !== undefined && normalizeTag(resolvedProgramId) === requestedId;
@@ -1886,11 +2287,17 @@ function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
1886
2287
  requestedPlatform.length > 0 &&
1887
2288
  candidatePlatform === requestedPlatform &&
1888
2289
  (candidateHandle.startsWith(`${requestedHandle}_`) || candidateHandle.startsWith(`${requestedHandle}-`));
2290
+ const looseNameMatch = requestedPlatform.length === 0 &&
2291
+ score >= 85 &&
2292
+ (candidateHandle === requestedHandle ||
2293
+ candidateName === requestedHandle ||
2294
+ candidateName.includes(requestedHandle) ||
2295
+ (candidateHandle.length > 0 && requestedHandle.includes(candidateHandle)));
1889
2296
  return {
1890
2297
  resolvedProgramId,
1891
2298
  program,
1892
2299
  score,
1893
- exactMatch: idMatch || platformHandleMatch || partialPlatformHandleMatch
2300
+ exactMatch: idMatch || platformHandleMatch || partialPlatformHandleMatch || looseNameMatch
1894
2301
  };
1895
2302
  })
1896
2303
  .filter((entry) => entry.resolvedProgramId !== undefined && entry.program !== undefined && entry.exactMatch && entry.score >= 80)
@@ -1914,8 +2321,8 @@ function withProgramIdFallbackMetadata(payload, fallback) {
1914
2321
  requested_program_id: fallback.requestedProgramId,
1915
2322
  resolved_program_id: fallback.resolvedProgramId,
1916
2323
  query: fallback.query,
1917
- reason: "stale_program_id",
1918
- stale_status: 404,
2324
+ reason: fallback.reason ?? "stale_program_id",
2325
+ stale_status: fallback.staleRequestId || fallback.staleUpstreamRequestId ? 404 : undefined,
1919
2326
  stale_request_id: fallback.staleRequestId,
1920
2327
  stale_upstream_request_id: fallback.staleUpstreamRequestId,
1921
2328
  resolution_request_id: stringField(fallback.resolution, "request_id")
@@ -1951,6 +2358,7 @@ async function resolveProgram(client, config, input) {
1951
2358
  const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
1952
2359
  const searchQueries = programResolutionSearchQueries(input.query);
1953
2360
  const collected = await collectProgramsForResolution(client, {
2361
+ query: input.query,
1954
2362
  searchQueries,
1955
2363
  isHighConfidenceMatch: (programs) => hasHighConfidenceProgramResolutionMatch(programs, config.webBaseUrl, input.query),
1956
2364
  scorePrograms: (programs) => bestProgramResolutionScore(programs, config.webBaseUrl, input.query),
@@ -1961,7 +2369,9 @@ async function resolveProgram(client, config, input) {
1961
2369
  budget,
1962
2370
  warnings
1963
2371
  });
1964
- const matches = collected.programs
2372
+ const sourceRequests = [...collected.sourceRequests];
2373
+ let targetLookup;
2374
+ let matches = collected.programs
1965
2375
  .map((program) => {
1966
2376
  const candidate = toProgramCandidate(program, config.webBaseUrl);
1967
2377
  const match = programResolutionMatch(candidate, input.query);
@@ -1970,14 +2380,58 @@ async function resolveProgram(client, config, input) {
1970
2380
  .filter((entry) => entry.match.score > 0)
1971
2381
  .sort((left, right) => right.match.score - left.match.score || compareProgramCandidates(left.candidate, right.candidate, "best_hunt_value"))
1972
2382
  .slice(0, input.max_results);
2383
+ if (matches.length === 0 && queryLooksLikeTargetIdentifier(input.query)) {
2384
+ targetLookup = await findProgramsByTarget(client, config, {
2385
+ target_query: input.query,
2386
+ match_mode: "smart",
2387
+ platforms: input.platforms,
2388
+ tags: input.tags,
2389
+ use_api_search: true,
2390
+ max_pages: 1,
2391
+ max_results: input.max_results,
2392
+ include_out_of_scope: false,
2393
+ include_ineligible: false,
2394
+ max_targets_per_program: 3,
2395
+ target_list_mode: "identifiers",
2396
+ upstream_request_budget: Math.max(1, budget.remaining)
2397
+ });
2398
+ sourceRequests.push(...readArray(targetLookup.source_requests).filter((request) => readObject(request) !== undefined));
2399
+ for (const warning of readArray(targetLookup.warnings)) {
2400
+ if (typeof warning === "string") {
2401
+ addUniqueWarning(warnings, warning);
2402
+ }
2403
+ }
2404
+ const targetBudget = readObject(targetLookup.upstream_budget);
2405
+ const targetBudgetUsed = numberField(targetBudget, "used") ?? 0;
2406
+ budget.remaining = Math.max(0, budget.remaining - targetBudgetUsed);
2407
+ matches = readArray(targetLookup.matches)
2408
+ .map((entry) => {
2409
+ const matchEntry = readObject(entry);
2410
+ const program = readObject(matchEntry?.program);
2411
+ if (!program) {
2412
+ return undefined;
2413
+ }
2414
+ return {
2415
+ candidate: toProgramCandidate(program, config.webBaseUrl),
2416
+ match: {
2417
+ score: 75,
2418
+ fields: ["targets"],
2419
+ reasons: ["target search matched query"]
2420
+ }
2421
+ };
2422
+ })
2423
+ .filter((entry) => entry !== undefined)
2424
+ .slice(0, input.max_results);
2425
+ }
1973
2426
  const programs = matches.map((entry) => formatProgram(entry.candidate.program, input.output_mode));
1974
2427
  const payload = stripUndefined({
1975
2428
  request_id: randomUUID(),
1976
- source_requests: collected.sourceRequests,
2429
+ source_requests: sourceRequests,
1977
2430
  warnings: warnings.length > 0 ? warnings : undefined,
1978
2431
  query: {
1979
2432
  text: input.query,
1980
2433
  search_queries: searchQueries,
2434
+ target_search_queries: readObject(targetLookup?.filters)?.target_search_queries,
1981
2435
  platforms: input.platforms.length > 0 ? input.platforms : undefined,
1982
2436
  tags: input.tags.length > 0 ? input.tags : undefined
1983
2437
  },
@@ -2002,7 +2456,8 @@ async function resolveProgram(client, config, input) {
2002
2456
  meta: {
2003
2457
  returned: programs.length,
2004
2458
  programs_scanned: collected.programsScanned,
2005
- upstream_requests_scanned: collected.requestsScanned
2459
+ upstream_requests_scanned: collected.requestsScanned,
2460
+ target_lookup_returned: numberField(readObject(targetLookup?.meta), "returned")
2006
2461
  }
2007
2462
  });
2008
2463
  rememberResolveProgram(config, input, payload);
@@ -2695,16 +3150,43 @@ function findRecentByType(client, config, input) {
2695
3150
  }
2696
3151
  async function findProgramsByTarget(client, config, input) {
2697
3152
  const warnings = [];
3153
+ const budget = {
3154
+ initial: input.upstream_request_budget,
3155
+ remaining: input.upstream_request_budget
3156
+ };
3157
+ const sourceRequests = [];
3158
+ const matches = [];
3159
+ const directHandles = await collectDirectHandlePrograms(client, {
3160
+ query: input.target_query,
3161
+ platforms: input.platforms,
3162
+ budget,
3163
+ warnings,
3164
+ source: "target_query_program_id_guess",
3165
+ maxProgramIds: 1,
3166
+ stopOnFirstMatch: true
3167
+ });
3168
+ sourceRequests.push(...directHandles.sourceRequests);
3169
+ await appendProgramTargetMatches(client, input, budget, warnings, sourceRequests, matches, directHandles.programs.map((program) => toProgramCandidate(program, config.webBaseUrl)));
3170
+ if (matches.length > 0 && input.match_mode === "smart") {
3171
+ return programsByTargetResult(input, warnings, sourceRequests, matches, budget, {
3172
+ source: "direct_program_handle_targets",
3173
+ rankingScope: {
3174
+ mode: "direct_program_handle",
3175
+ possibly_incomplete: false
3176
+ },
3177
+ meta: {
3178
+ returned: matches.length,
3179
+ programs_scanned: directHandles.programsScanned,
3180
+ target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
3181
+ }
3182
+ });
3183
+ }
2698
3184
  if (input.use_api_search) {
2699
3185
  const directResult = await searchProgramsByTargetApi(client, config, input, warnings);
2700
3186
  if (directResult) {
2701
3187
  return directResult;
2702
3188
  }
2703
3189
  }
2704
- const budget = {
2705
- initial: input.upstream_request_budget,
2706
- remaining: input.upstream_request_budget
2707
- };
2708
3190
  const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
2709
3191
  const collected = await collectPrograms(client, {
2710
3192
  platforms: input.platforms,
@@ -2715,11 +3197,27 @@ async function findProgramsByTarget(client, config, input) {
2715
3197
  budget,
2716
3198
  warnings
2717
3199
  });
2718
- const sourceRequests = [...collected.sourceRequests];
2719
- const matches = [];
3200
+ sourceRequests.push(...collected.sourceRequests);
2720
3201
  const candidates = collected.programs
2721
3202
  .map((program) => toProgramCandidate(program, config.webBaseUrl))
2722
3203
  .sort((left, right) => compareProgramCandidates(left, right, "best_hunt_value"));
3204
+ await appendProgramTargetMatches(client, input, budget, warnings, sourceRequests, matches, candidates);
3205
+ return programsByTargetResult(input, warnings, sourceRequests, matches, budget, {
3206
+ source: "sampled_program_targets",
3207
+ rankingScope: {
3208
+ mode: "sampled",
3209
+ max_pages_scanned_per_source: maxPages,
3210
+ upstream_total_pages: collected.totalPagesBySource,
3211
+ possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, maxPages)
3212
+ },
3213
+ meta: {
3214
+ returned: matches.length,
3215
+ programs_scanned: directHandles.programsScanned + collected.programsScanned,
3216
+ target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
3217
+ }
3218
+ });
3219
+ }
3220
+ async function appendProgramTargetMatches(client, input, budget, warnings, sourceRequests, matches, candidates) {
2723
3221
  for (const candidate of candidates) {
2724
3222
  if (matches.length >= input.max_results) {
2725
3223
  break;
@@ -2729,7 +3227,9 @@ async function findProgramsByTarget(client, config, input) {
2729
3227
  continue;
2730
3228
  }
2731
3229
  if (!tryConsumeBudget(budget)) {
2732
- addUniqueWarning(warnings, "Upstream request budget was exhausted before all candidate target lists could be checked.");
3230
+ if (matches.length === 0) {
3231
+ addUniqueWarning(warnings, "Upstream request budget was exhausted before all candidate target lists could be checked.");
3232
+ }
2733
3233
  break;
2734
3234
  }
2735
3235
  const targetsApi = await client.getProgramTargets(programId);
@@ -2761,6 +3261,8 @@ async function findProgramsByTarget(client, config, input) {
2761
3261
  total_targets_checked: targets.length
2762
3262
  }));
2763
3263
  }
3264
+ }
3265
+ function programsByTargetResult(input, warnings, sourceRequests, matches, budget, options) {
2764
3266
  return stripUndefined({
2765
3267
  request_id: randomUUID(),
2766
3268
  source_requests: sourceRequests,
@@ -2770,7 +3272,7 @@ async function findProgramsByTarget(client, config, input) {
2770
3272
  match_mode: input.match_mode,
2771
3273
  platforms: input.platforms.length > 0 ? input.platforms : undefined,
2772
3274
  tags: input.tags.length > 0 ? input.tags : undefined,
2773
- source: "sampled_program_targets",
3275
+ source: options.source,
2774
3276
  include_out_of_scope: input.include_out_of_scope,
2775
3277
  include_ineligible: input.include_ineligible,
2776
3278
  target_list_mode: input.target_list_mode
@@ -2781,63 +3283,79 @@ async function findProgramsByTarget(client, config, input) {
2781
3283
  used: budget.initial - budget.remaining,
2782
3284
  remaining: budget.remaining
2783
3285
  },
2784
- ranking_scope: {
2785
- mode: "sampled",
2786
- max_pages_scanned_per_source: maxPages,
2787
- upstream_total_pages: collected.totalPagesBySource,
2788
- possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, maxPages)
2789
- },
2790
- meta: {
2791
- returned: matches.length,
2792
- programs_scanned: collected.programsScanned,
2793
- target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
2794
- }
3286
+ ranking_scope: options.rankingScope,
3287
+ meta: options.meta
2795
3288
  });
2796
3289
  }
2797
3290
  async function searchProgramsByTargetApi(client, config, input, warnings) {
2798
- let api;
2799
- try {
2800
- api = await client.searchTargets({
2801
- q: input.target_query,
2802
- match_mode: input.match_mode,
2803
- platforms: input.platforms,
2804
- tags: input.tags,
2805
- include_out_of_scope: input.include_out_of_scope,
2806
- include_ineligible: input.include_ineligible,
2807
- page: 1,
2808
- page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
2809
- });
2810
- }
2811
- catch (error) {
2812
- if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
2813
- warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
2814
- return undefined;
2815
- }
2816
- throw error;
2817
- }
2818
- const data = readObject(api.data);
2819
- const rawMatches = readTargetSearchRows(data);
3291
+ const searchQueries = targetApiSearchQueries(input.target_query);
2820
3292
  const grouped = new Map();
2821
- for (const row of rawMatches) {
2822
- const program = addProgramResourceLinks(sanitizeProgram(readObject(row.program) ?? readObject(readObject(row.target)?.program) ?? row, config.webBaseUrl));
2823
- const programId = stringField(program, "id") ?? stringField(row, "program_id");
2824
- const target = sanitizeTarget(readObject(row.target) ?? row);
2825
- if (!programId || !targetIdentifier(target)) {
2826
- continue;
3293
+ const sourceRequests = [];
3294
+ let rawMatchesScanned = 0;
3295
+ let lastMeta;
3296
+ for (const searchQuery of searchQueries) {
3297
+ let api;
3298
+ try {
3299
+ api = await client.searchTargets({
3300
+ q: searchQuery,
3301
+ match_mode: upstreamTargetMatchMode(input.match_mode),
3302
+ platforms: input.platforms,
3303
+ tags: input.tags,
3304
+ include_out_of_scope: input.include_out_of_scope,
3305
+ include_ineligible: input.include_ineligible,
3306
+ page: 1,
3307
+ page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
3308
+ });
2827
3309
  }
2828
- if (!targetHasAllowedScope(target, {
2829
- include_out_of_scope: input.include_out_of_scope,
2830
- include_ineligible: input.include_ineligible,
2831
- strict_scope_filter: false
2832
- })) {
2833
- continue;
3310
+ catch (error) {
3311
+ if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
3312
+ warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
3313
+ return undefined;
3314
+ }
3315
+ throw error;
2834
3316
  }
2835
- if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
2836
- continue;
3317
+ const data = readObject(api.data);
3318
+ const rawMatches = readTargetSearchRows(data);
3319
+ rawMatchesScanned += rawMatches.length;
3320
+ lastMeta = readObject(sanitizeJson(data?.meta)) ?? lastMeta;
3321
+ sourceRequests.push({
3322
+ source: "target_search",
3323
+ query: searchQuery,
3324
+ api_match_mode: upstreamTargetMatchMode(input.match_mode),
3325
+ request_id: api.requestId,
3326
+ upstream_request_id: api.upstreamRequestId,
3327
+ ...apiSourceMetadata(api)
3328
+ });
3329
+ for (const row of rawMatches) {
3330
+ const rowProgram = sanitizeProgram(readObject(row.program) ?? row, config.webBaseUrl);
3331
+ const rowProgramId = stringField(rowProgram, "id") ?? stringField(row, "program_id");
3332
+ const targets = targetSearchRowTargets(row);
3333
+ for (const targetSource of targets) {
3334
+ const target = sanitizeTarget(targetSource);
3335
+ const nestedProgram = sanitizeProgram(readObject(targetSource.program), config.webBaseUrl);
3336
+ const program = stringField(nestedProgram, "id") ? nestedProgram : rowProgram;
3337
+ const programId = stringField(program, "id") ?? rowProgramId ?? stringField(targetSource, "program_id");
3338
+ if (!programId || !targetIdentifier(target)) {
3339
+ continue;
3340
+ }
3341
+ if (!targetHasAllowedScope(target, {
3342
+ include_out_of_scope: input.include_out_of_scope,
3343
+ include_ineligible: input.include_ineligible,
3344
+ strict_scope_filter: false
3345
+ })) {
3346
+ continue;
3347
+ }
3348
+ if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
3349
+ continue;
3350
+ }
3351
+ const existing = grouped.get(programId) ?? { program: addProgramResourceLinks({ ...program, id: programId }), targets: [] };
3352
+ existing.targets.push(target);
3353
+ grouped.set(programId, existing);
3354
+ }
3355
+ }
3356
+ if (grouped.size >= input.max_results) {
3357
+ break;
2837
3358
  }
2838
- const existing = grouped.get(programId) ?? { program: { ...program, id: programId }, targets: [] };
2839
- existing.targets.push(target);
2840
- grouped.set(programId, existing);
2841
3359
  }
2842
3360
  const matches = [...grouped.values()].slice(0, input.max_results).map((entry) => stripUndefined({
2843
3361
  program: formatProgram(entry.program, "compact"),
@@ -2845,20 +3363,17 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
2845
3363
  matching_targets: formatTargetList(entry.targets.slice(0, input.max_targets_per_program), input.target_list_mode),
2846
3364
  matching_targets_has_more: entry.targets.length > input.max_targets_per_program
2847
3365
  }));
2848
- const sanitizedMeta = readObject(sanitizeJson(data?.meta));
3366
+ if (matches.length === 0 && input.match_mode === "smart") {
3367
+ warnings.push("Direct target search returned no smart matches; fell back to sampled program target lists.");
3368
+ return undefined;
3369
+ }
2849
3370
  return stripUndefined({
2850
3371
  request_id: randomUUID(),
2851
- source_requests: [
2852
- {
2853
- source: "target_search",
2854
- request_id: api.requestId,
2855
- upstream_request_id: api.upstreamRequestId,
2856
- ...apiSourceMetadata(api)
2857
- }
2858
- ],
3372
+ source_requests: sourceRequests,
2859
3373
  warnings: warnings.length > 0 ? warnings : undefined,
2860
3374
  filters: {
2861
3375
  target_query: input.target_query,
3376
+ target_search_queries: searchQueries.length > 1 ? searchQueries : undefined,
2862
3377
  match_mode: input.match_mode,
2863
3378
  platforms: input.platforms.length > 0 ? input.platforms : undefined,
2864
3379
  tags: input.tags.length > 0 ? input.tags : undefined,
@@ -2870,19 +3385,52 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
2870
3385
  matches,
2871
3386
  programs: matches.map((match) => readObject(match.program)).filter((program) => program !== undefined),
2872
3387
  meta: stripUndefined({
2873
- ...(sanitizedMeta ?? {}),
3388
+ ...(lastMeta ?? {}),
2874
3389
  returned: matches.length,
2875
- target_search_rows_scanned: rawMatches.length
3390
+ target_search_rows_scanned: rawMatchesScanned
2876
3391
  })
2877
3392
  });
2878
3393
  }
2879
3394
  function readTargetSearchRows(data) {
2880
- return [data?.results, data?.matches, data?.targets]
3395
+ return [data?.results, data?.matches, data?.programs, data?.targets]
2881
3396
  .map(readArray)
2882
3397
  .find((rows) => rows.length > 0)
2883
3398
  ?.map((row) => readObject(row))
2884
3399
  .filter((row) => row !== undefined) ?? [];
2885
3400
  }
3401
+ function targetSearchRowTargets(row) {
3402
+ const directTarget = readObject(row.target);
3403
+ const arrays = [
3404
+ row.targets,
3405
+ row.matched_targets,
3406
+ row.matching_targets,
3407
+ row.target_matches,
3408
+ row.scope
3409
+ ]
3410
+ .flatMap(readArray)
3411
+ .map(targetSearchEntryToTarget)
3412
+ .filter((target) => target !== undefined);
3413
+ if (directTarget) {
3414
+ return [directTarget, ...arrays];
3415
+ }
3416
+ if (arrays.length > 0) {
3417
+ return arrays;
3418
+ }
3419
+ if (targetIdentifier(row)) {
3420
+ return [row];
3421
+ }
3422
+ return [];
3423
+ }
3424
+ function targetSearchEntryToTarget(entry) {
3425
+ if (typeof entry === "string") {
3426
+ return { identifier: entry };
3427
+ }
3428
+ const object = readObject(entry);
3429
+ if (!object) {
3430
+ return undefined;
3431
+ }
3432
+ return readObject(object.target) ?? object;
3433
+ }
2886
3434
  function rewardThresholdFilters(minReward, mode) {
2887
3435
  return {
2888
3436
  min_bounty_min: minReward !== undefined && mode === "min_at_least" ? minReward : undefined,
@@ -4064,7 +4612,8 @@ async function checkProgramNewTargetsFromIndexedProgram(client, config, input, i
4064
4612
  });
4065
4613
  }
4066
4614
  async function checkWatchlistNewTargets(client, config, input) {
4067
- const watchlist = new Set(input.program_ids);
4615
+ const resolvedProgramIds = await resolveProgramIdentifiers(client, config, input.program_ids);
4616
+ const watchlist = new Set(resolvedProgramIds.entries.map((entry) => entry.resolvedProgramId));
4068
4617
  const api = await client.getRecentChanges({
4069
4618
  change_type: "added",
4070
4619
  include_removed: false,
@@ -4079,8 +4628,8 @@ async function checkWatchlistNewTargets(client, config, input) {
4079
4628
  const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
4080
4629
  const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
4081
4630
  const grouped = new Map();
4082
- for (const programId of input.program_ids) {
4083
- grouped.set(programId, {
4631
+ for (const entry of resolvedProgramIds.entries) {
4632
+ grouped.set(entry.resolvedProgramId, {
4084
4633
  program: undefined,
4085
4634
  changes: [],
4086
4635
  targets: [],
@@ -4118,12 +4667,13 @@ async function checkWatchlistNewTargets(client, config, input) {
4118
4667
  existing.latestAddedAt = stringField(change, "changed_at");
4119
4668
  }
4120
4669
  }
4121
- const programs = input.program_ids.map((programId) => {
4122
- const entry = grouped.get(programId);
4670
+ const programs = resolvedProgramIds.entries.map((programEntry) => {
4671
+ const entry = grouped.get(programEntry.resolvedProgramId);
4123
4672
  const targets = entry?.targets ?? [];
4124
4673
  const limitedTargets = targets.slice(0, input.max_targets_per_program);
4125
4674
  return stripUndefined({
4126
- program_id: programId,
4675
+ program_id: programEntry.resolvedProgramId,
4676
+ requested_program_id: programEntry.resolved ? programEntry.requestedProgramId : undefined,
4127
4677
  program: entry?.program ? formatProgram(entry.program, "compact") : undefined,
4128
4678
  has_new_targets: targets.length > 0,
4129
4679
  new_target_count: targets.length,
@@ -4135,6 +4685,7 @@ async function checkWatchlistNewTargets(client, config, input) {
4135
4685
  return stripUndefined({
4136
4686
  request_id: randomUUID(),
4137
4687
  source_requests: [
4688
+ ...resolvedProgramIds.sourceRequests,
4138
4689
  {
4139
4690
  source: "recent_changes",
4140
4691
  request_id: api.requestId,
@@ -4144,6 +4695,7 @@ async function checkWatchlistNewTargets(client, config, input) {
4144
4695
  ],
4145
4696
  any_new_targets: programs.some((program) => program.has_new_targets === true),
4146
4697
  programs,
4698
+ warnings: resolvedProgramIds.warnings.length > 0 ? resolvedProgramIds.warnings : undefined,
4147
4699
  meta: stripUndefined({
4148
4700
  recent_changes_scanned: changes.length,
4149
4701
  watchlist_size: input.program_ids.length,
@@ -4274,7 +4826,7 @@ function addProgramResourceLinks(program) {
4274
4826
  };
4275
4827
  }
4276
4828
  function parseProgramIdFromResourceUri(uri) {
4277
- return programIdSchema.parse(uri.searchParams.get("program_id") ?? "");
4829
+ return programIdentifierSchema.parse(uri.searchParams.get("program_id") ?? "");
4278
4830
  }
4279
4831
  function programResourceUri(programId) {
4280
4832
  return `bbradar://program?program_id=${encodeURIComponent(programId)}`;
@@ -4858,7 +5410,7 @@ function programFetchError(programId, error) {
4858
5410
  message: error.message,
4859
5411
  detail: sanitizeJson(error.detail),
4860
5412
  errors: sanitizeJson(error.errors),
4861
- suggested_fix: "Check that the program_id uses BBRadar platform:handle format. Spaces and slashes are supported and are encoded by the MCP server."
5413
+ suggested_fix: "Use a BBRadar platform:handle program_id or call resolve_program with the human program name and retry with one returned program_id."
4862
5414
  });
4863
5415
  }
4864
5416
  return {
@@ -4884,6 +5436,12 @@ function apiOperationError(operation, error) {
4884
5436
  message: error instanceof Error ? error.message : String(error)
4885
5437
  };
4886
5438
  }
5439
+ function unresolvedProgramNameError(programName) {
5440
+ return new Error(`Could not resolve program name "${programName}" to one unambiguous BBRadar program_id. Call resolve_program with the name, then retry with a returned platform:handle program_id.`);
5441
+ }
5442
+ function isStrictProgramId(value) {
5443
+ return programIdSchema.safeParse(value).success;
5444
+ }
4887
5445
  function programSearchText(programId) {
4888
5446
  const colonIndex = programId.indexOf(":");
4889
5447
  const handle = colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
@@ -5040,9 +5598,27 @@ async function collectProgramsForResolution(client, options) {
5040
5598
  const collections = [];
5041
5599
  const queryCollections = [];
5042
5600
  const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
5601
+ const primaryDirectProgramIds = new Set(directProgramIdCandidates(options.query, options.platforms).slice(0, 1));
5602
+ if (primaryDirectProgramIds.size > 0 && options.budget.remaining > 0) {
5603
+ const primaryDirectHandles = await collectDirectHandlePrograms(client, {
5604
+ query: options.query,
5605
+ platforms: options.platforms,
5606
+ budget: options.budget,
5607
+ warnings: options.warnings,
5608
+ source: "program_id_handle_guess",
5609
+ maxProgramIds: 1,
5610
+ stopOnFirstMatch: true
5611
+ });
5612
+ if (primaryDirectHandles.programs.length > 0 || primaryDirectHandles.requestsScanned > 0) {
5613
+ collections.push(primaryDirectHandles);
5614
+ }
5615
+ if (primaryDirectHandles.programs.length > 0 && options.isHighConfidenceMatch?.(mergeProgramCollections(collections).programs)) {
5616
+ return mergeProgramCollections(collections);
5617
+ }
5618
+ }
5043
5619
  for (const search of queries) {
5044
5620
  if (options.budget.remaining <= 0) {
5045
- addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all program name search variants could be checked.");
5621
+ addUniqueWarning(options.warnings, "Program name search stopped at the request budget; pass a higher upstream_request_budget for a deeper resolution attempt.");
5046
5622
  break;
5047
5623
  }
5048
5624
  const collection = await collectPrograms(client, {
@@ -5061,6 +5637,23 @@ async function collectProgramsForResolution(client, options) {
5061
5637
  return mergeProgramCollections(collections);
5062
5638
  }
5063
5639
  }
5640
+ if (options.budget.remaining > 0) {
5641
+ const directHandles = await collectDirectHandlePrograms(client, {
5642
+ query: options.query,
5643
+ platforms: options.platforms,
5644
+ budget: options.budget,
5645
+ warnings: options.warnings,
5646
+ source: "program_id_handle_guess",
5647
+ skipProgramIds: primaryDirectProgramIds,
5648
+ stopOnFirstMatch: true
5649
+ });
5650
+ if (directHandles.programs.length > 0 || directHandles.requestsScanned > 0) {
5651
+ collections.push(directHandles);
5652
+ }
5653
+ if (directHandles.programs.length > 0 && options.isHighConfidenceMatch?.(mergeProgramCollections(collections).programs)) {
5654
+ return mergeProgramCollections(collections);
5655
+ }
5656
+ }
5064
5657
  if (options.max_pages > 1) {
5065
5658
  const search = bestResolutionDeepSearch(queryCollections, options.scorePrograms) ?? queries[0];
5066
5659
  if (options.budget.remaining <= 0) {
@@ -5121,6 +5714,152 @@ function mergeProgramCollections(collections) {
5121
5714
  totalPagesBySource
5122
5715
  };
5123
5716
  }
5717
+ async function collectDirectHandlePrograms(client, options) {
5718
+ const programIds = directProgramIdCandidates(options.query, options.platforms)
5719
+ .filter((programId) => !options.skipProgramIds?.has(programId))
5720
+ .slice(0, options.maxProgramIds ?? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
5721
+ const programs = [];
5722
+ const sourceRequests = [];
5723
+ let requestsScanned = 0;
5724
+ for (const programId of programIds) {
5725
+ if (!tryConsumeBudget(options.budget)) {
5726
+ break;
5727
+ }
5728
+ requestsScanned += 1;
5729
+ try {
5730
+ const api = await client.getProgram(programId);
5731
+ programs.push(api.data);
5732
+ sourceRequests.push({
5733
+ source: options.source,
5734
+ program_id: programId,
5735
+ request_id: api.requestId,
5736
+ upstream_request_id: api.upstreamRequestId,
5737
+ ...apiSourceMetadata(api)
5738
+ });
5739
+ if (options.stopOnFirstMatch) {
5740
+ break;
5741
+ }
5742
+ }
5743
+ catch (error) {
5744
+ if (error instanceof BBRadarApiError && error.status === 404) {
5745
+ sourceRequests.push({
5746
+ source: options.source,
5747
+ endpoint: "program_detail",
5748
+ program_id: programId,
5749
+ request_id: error.requestId,
5750
+ upstream_request_id: error.upstreamRequestId,
5751
+ status: error.status
5752
+ });
5753
+ if (!tryConsumeBudget(options.budget)) {
5754
+ break;
5755
+ }
5756
+ requestsScanned += 1;
5757
+ try {
5758
+ const targetsApi = await client.getProgramTargets(programId);
5759
+ const targetsData = readObject(targetsApi.data);
5760
+ const targets = readArray(targetsData?.targets).map(sanitizeTarget);
5761
+ if (targets.length > 0) {
5762
+ programs.push(syntheticProgramFromProgramId(programId, targets));
5763
+ }
5764
+ sourceRequests.push({
5765
+ source: options.source,
5766
+ endpoint: "program_targets",
5767
+ program_id: programId,
5768
+ request_id: targetsApi.requestId,
5769
+ upstream_request_id: targetsApi.upstreamRequestId,
5770
+ ...apiSourceMetadata(targetsApi)
5771
+ });
5772
+ if (targets.length > 0 && options.stopOnFirstMatch) {
5773
+ break;
5774
+ }
5775
+ }
5776
+ catch (targetError) {
5777
+ if (targetError instanceof BBRadarApiError && targetError.status === 404) {
5778
+ sourceRequests.push({
5779
+ source: options.source,
5780
+ endpoint: "program_targets",
5781
+ program_id: programId,
5782
+ request_id: targetError.requestId,
5783
+ upstream_request_id: targetError.upstreamRequestId,
5784
+ status: targetError.status
5785
+ });
5786
+ continue;
5787
+ }
5788
+ throw targetError;
5789
+ }
5790
+ continue;
5791
+ }
5792
+ throw error;
5793
+ }
5794
+ }
5795
+ return {
5796
+ programs,
5797
+ sourceRequests,
5798
+ requestsScanned,
5799
+ programsScanned: programs.length,
5800
+ totalPagesBySource: programs.length > 0 ? { [options.source]: 1 } : {}
5801
+ };
5802
+ }
5803
+ function syntheticProgramFromProgramId(programId, targets) {
5804
+ const separatorIndex = programId.indexOf(":");
5805
+ const platform = separatorIndex >= 0 ? programId.slice(0, separatorIndex) : undefined;
5806
+ const handle = separatorIndex >= 0 ? programId.slice(separatorIndex + 1) : programId;
5807
+ const scopeTags = uniqueStrings(targets.flatMap((target) => readStringArrayField(target, "scope_tags")));
5808
+ return stripUndefined({
5809
+ id: programId,
5810
+ handle,
5811
+ name: titleizeProgramHandle(handle),
5812
+ platform,
5813
+ scope_tags: scopeTags,
5814
+ target_counts: {
5815
+ total: targets.length,
5816
+ in_scope: targets.filter((target) => target.in_scope === true).length,
5817
+ out_of_scope: targets.filter((target) => target.in_scope === false).length,
5818
+ eligible_for_bounty: targets.filter((target) => target.eligible_for_bounty === true).length,
5819
+ wildcard: targets.filter(targetIsWildcard).length
5820
+ }
5821
+ });
5822
+ }
5823
+ function titleizeProgramHandle(handle) {
5824
+ return handle
5825
+ .split(/[-_.\s]+/)
5826
+ .filter(Boolean)
5827
+ .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
5828
+ .join(" ");
5829
+ }
5830
+ function directProgramIdCandidates(query, platforms) {
5831
+ const platformGuesses = (platforms.length > 0 ? platforms : PROGRAM_ID_PLATFORM_GUESSES).map(canonicalProgramIdPlatform);
5832
+ const handles = directProgramHandleVariants(query);
5833
+ return uniqueStrings(platformGuesses.flatMap((platform) => handles.map((handle) => `${platform}:${handle}`))).slice(0, MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
5834
+ }
5835
+ function canonicalProgramIdPlatform(platform) {
5836
+ const normalized = normalizeTag(platform).replace(/[^a-z0-9]/g, "");
5837
+ const knownPlatforms = {
5838
+ hackerone: "HackerOne",
5839
+ bugcrowd: "Bugcrowd",
5840
+ intigriti: "Intigriti",
5841
+ hackenproof: "HackenProof",
5842
+ yeswehack: "YesWeHack",
5843
+ cantina: "Cantina",
5844
+ code4rena: "Code4rena",
5845
+ immunefi: "Immunefi"
5846
+ };
5847
+ return knownPlatforms[normalized] ?? platform.trim();
5848
+ }
5849
+ function directProgramHandleVariants(query) {
5850
+ const handleText = query.includes(":") ? query.slice(query.indexOf(":") + 1) : query;
5851
+ const normalized = normalizeTargetIdentifierForMatch(handleText);
5852
+ const dashed = normalized.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5853
+ const compact = dashed.replace(/-/g, "");
5854
+ const tokens = [...meaningfulProgramQueryTokens(handleText)];
5855
+ return uniqueStrings([
5856
+ ...targetBrandTokens(handleText),
5857
+ dashed,
5858
+ compact,
5859
+ tokens.join("-"),
5860
+ tokens.join("")
5861
+ ]).filter((handle) => /^[a-z0-9][a-z0-9_.-]{3,100}$/i.test(handle));
5862
+ }
5124
5863
  async function collectProgramSources(client, sources, options) {
5125
5864
  const pages = [];
5126
5865
  await mapWithConcurrency(sources.map((source, sourceIndex) => ({ source, sourceIndex })), PROGRAM_COLLECTION_CONCURRENCY, async ({ source, sourceIndex }) => {
@@ -5613,7 +6352,7 @@ Find the best BBRadar program candidates.
5613
6352
  title: "Summarize Program Scope",
5614
6353
  description: "Summarize BBRadar target data for one program.",
5615
6354
  argsSchema: {
5616
- program_id: programIdSchema
6355
+ program_id: programIdentifierSchema
5617
6356
  }
5618
6357
  }, (args) => promptResult(`Use get_program_targets for the program_id in this JSON string: ${promptJson(args.program_id)} with include_out_of_scope=false, include_ineligible=false, and limit=${MAX_TARGETS_PER_PROGRAM}. Do not call get_program first unless program metadata is explicitly needed. Only request out-of-scope or ineligible targets if the user explicitly asks for them.
5619
6358
 
@@ -5628,7 +6367,7 @@ Summarize the program scope:
5628
6367
  title: "Prepare Passive Recon Plan",
5629
6368
  description: "Create a passive-only plan from BBRadar program and target data. Use only when planning is explicitly requested.",
5630
6369
  argsSchema: {
5631
- program_id: programIdSchema.optional(),
6370
+ program_id: programIdentifierSchema.optional(),
5632
6371
  focus: z.string().optional().describe("Optional focus area such as APIs, domains, mobile, or cloud.")
5633
6372
  }
5634
6373
  }, (args) => promptResult(`Prepare a passive-only plan from BBRadar data. Program id JSON string, if present: ${promptJson(args.program_id ?? "")}.
@@ -5871,32 +6610,183 @@ function targetMatchesRequestedType(target, targetType) {
5871
6610
  return actualTargetType === normalizedTargetType || readStringArrayField(target, "scope_tags").map(normalizeTag).includes(normalizedTargetType);
5872
6611
  }
5873
6612
  function targetMatchesQuery(target, query, mode) {
5874
- const identifier = targetIdentifier(target);
5875
- if (!identifier) {
6613
+ const profile = targetSearchProfile(target);
6614
+ if (profile.values.length === 0) {
6615
+ return false;
6616
+ }
6617
+ const queryValues = targetQueryVariants(query);
6618
+ if (queryValues.length === 0) {
5876
6619
  return false;
5877
6620
  }
5878
- const normalizedIdentifier = normalizeTargetIdentifierForMatch(identifier);
5879
- const normalizedQuery = normalizeTargetIdentifierForMatch(query);
5880
6621
  if (mode === "exact") {
5881
- return normalizedIdentifier === normalizedQuery;
6622
+ return profile.values.some((value) => queryValues.some((queryValue) => value === queryValue));
5882
6623
  }
5883
6624
  if (mode === "suffix") {
5884
- return (normalizedIdentifier === normalizedQuery ||
5885
- normalizedIdentifier.endsWith(`.${normalizedQuery}`) ||
5886
- normalizedQuery.endsWith(`.${normalizedIdentifier}`));
6625
+ return profile.values.some((value) => queryValues.some((queryValue) => targetSuffixMatch(value, queryValue)));
5887
6626
  }
5888
6627
  if (mode === "wildcard") {
5889
- return wildcardTargetMatch(normalizedQuery, normalizedIdentifier) || wildcardTargetMatch(normalizedIdentifier, normalizedQuery);
6628
+ return profile.values.some((value) => queryValues.some((queryValue) => targetWildcardMatch(value, queryValue)));
6629
+ }
6630
+ const containsMatch = profile.values.some((value) => queryValues.some((queryValue) => targetContainsMatch(value, queryValue)));
6631
+ if (mode === "contains") {
6632
+ return containsMatch;
6633
+ }
6634
+ return (containsMatch ||
6635
+ profile.values.some((value) => queryValues.some((queryValue) => targetSuffixMatch(value, queryValue) || targetWildcardMatch(value, queryValue))) ||
6636
+ targetTokensMatch(profile.tokens, query));
6637
+ }
6638
+ function upstreamTargetMatchMode(mode) {
6639
+ return mode === "smart" ? "contains" : mode;
6640
+ }
6641
+ function targetSearchProfile(target) {
6642
+ const scalarFields = [
6643
+ "identifier",
6644
+ "display_name",
6645
+ "normalized_identifier",
6646
+ "target_type",
6647
+ "name",
6648
+ "url",
6649
+ "uri",
6650
+ "link",
6651
+ "repository",
6652
+ "repo",
6653
+ "package",
6654
+ "package_name",
6655
+ "bundle_id",
6656
+ "app_id",
6657
+ "asset",
6658
+ "asset_identifier",
6659
+ "technology"
6660
+ ];
6661
+ const rawValues = [
6662
+ ...scalarFields.map((field) => stringField(target, field)),
6663
+ ...readStringArrayField(target, "scope_tags"),
6664
+ ...readStringArrayField(target, "language_tags"),
6665
+ ...readStringArrayField(target, "tags")
6666
+ ].filter((value) => value !== undefined);
6667
+ const values = uniqueStrings(rawValues.flatMap(targetStringVariants));
6668
+ return {
6669
+ values,
6670
+ tokens: targetTokenSet([...rawValues, ...values].join(" "))
6671
+ };
6672
+ }
6673
+ function targetQueryVariants(query) {
6674
+ return targetStringVariants(query);
6675
+ }
6676
+ function targetApiSearchQueries(query) {
6677
+ const normalized = normalizeTargetIdentifierForMatch(query);
6678
+ const brandDomains = targetBrandTokens(query).flatMap((token) => [`${token}.com`, `*.${token}.com`]);
6679
+ return uniqueStrings([
6680
+ normalized,
6681
+ ...targetQueryVariants(query),
6682
+ ...brandDomains
6683
+ ].filter((entry) => entry !== undefined && entry.length >= 2)).slice(0, MAX_RESOLVE_SEARCH_QUERIES);
6684
+ }
6685
+ function queryLooksLikeTargetIdentifier(query) {
6686
+ const normalized = normalizeTargetIdentifierForMatch(query);
6687
+ return /[./*:]/.test(normalized);
6688
+ }
6689
+ function targetStringVariants(value) {
6690
+ const normalized = normalizeTargetIdentifierForMatch(value);
6691
+ if (!normalized) {
6692
+ return [];
6693
+ }
6694
+ const withoutWildcard = stripLeadingTargetWildcard(normalized);
6695
+ const host = targetHostVariant(normalized);
6696
+ const pathVariants = targetPathVariants(normalized);
6697
+ const tokenPhrase = [...targetTokenSet(normalized)].join(" ");
6698
+ return uniqueStrings([
6699
+ normalized,
6700
+ withoutWildcard,
6701
+ host,
6702
+ host ? stripLeadingTargetWildcard(host) : undefined,
6703
+ ...pathVariants,
6704
+ tokenPhrase.length >= 2 ? tokenPhrase : undefined
6705
+ ].filter((entry) => entry !== undefined && entry.length >= 2));
6706
+ }
6707
+ function targetSuffixMatch(value, query) {
6708
+ const normalizedValue = stripLeadingTargetWildcard(value);
6709
+ const normalizedQuery = stripLeadingTargetWildcard(query);
6710
+ return (normalizedValue === normalizedQuery ||
6711
+ normalizedValue.endsWith(`.${normalizedQuery}`) ||
6712
+ normalizedQuery.endsWith(`.${normalizedValue}`));
6713
+ }
6714
+ function targetWildcardMatch(value, query) {
6715
+ return wildcardTargetMatch(query, value) || wildcardTargetMatch(value, query);
6716
+ }
6717
+ function targetContainsMatch(value, query) {
6718
+ if (value.includes(query)) {
6719
+ return true;
5890
6720
  }
5891
- return normalizedIdentifier.includes(normalizedQuery) || normalizedQuery.includes(normalizedIdentifier);
6721
+ return targetValueCanContainQuery(value) && query.includes(value);
6722
+ }
6723
+ function targetValueCanContainQuery(value) {
6724
+ return value.includes(".") || value.includes("/") || value.includes("*") || value.includes(":");
6725
+ }
6726
+ function targetTokensMatch(targetTokens, query) {
6727
+ const queryTokens = [...targetTokenSet(query)];
6728
+ if (queryTokens.length === 0) {
6729
+ return false;
6730
+ }
6731
+ const overlap = queryTokens.filter((token) => targetTokens.has(token)).length;
6732
+ if (queryTokens.length <= 2) {
6733
+ return overlap === queryTokens.length;
6734
+ }
6735
+ return overlap >= Math.ceil(queryTokens.length * 0.67);
6736
+ }
6737
+ function targetTokenSet(value) {
6738
+ const tokens = [...tokenSet(normalizeTargetIdentifierForMatch(value))];
6739
+ const meaningfulTokens = tokens.filter((token) => !TARGET_QUERY_STOP_WORDS.has(token));
6740
+ return new Set(meaningfulTokens.length > 0 ? meaningfulTokens : tokens);
6741
+ }
6742
+ function targetBrandTokens(value) {
6743
+ const normalized = stripLeadingTargetWildcard(normalizeTargetIdentifierForMatch(value));
6744
+ const host = targetHostVariant(normalized) ?? (normalized.includes(".") && !normalized.includes("/") ? normalized : undefined);
6745
+ const hostBrand = host
6746
+ ?.split(".")
6747
+ .filter(Boolean)
6748
+ .slice(-2, -1)
6749
+ .find((token) => token.length >= 3 && !TARGET_QUERY_STOP_WORDS.has(token));
6750
+ const textTokens = [...targetTokenSet(normalized)].filter((token) => token.length >= 4);
6751
+ return uniqueStrings([hostBrand, ...textTokens].filter((entry) => entry !== undefined));
5892
6752
  }
5893
6753
  function normalizeTargetIdentifierForMatch(value) {
5894
- return value
6754
+ const normalized = value
5895
6755
  .trim()
5896
6756
  .toLowerCase()
5897
- .replace(/^https?:\/\//, "")
5898
- .replace(/^www\./, "")
5899
- .replace(/\/+$/, "");
6757
+ .replace(/\\/g, "/")
6758
+ .replace(/^['"`<]+|['"`>]+$/g, "")
6759
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, "")
6760
+ .replace(/^www\./, "");
6761
+ const queryStart = normalized.search(/[?#]/);
6762
+ return (queryStart >= 0 ? normalized.slice(0, queryStart) : normalized).replace(/\/+$/, "");
6763
+ }
6764
+ function stripLeadingTargetWildcard(value) {
6765
+ return value.replace(/^\*\./, "").replace(/^\*\/?/, "");
6766
+ }
6767
+ function targetHostVariant(value) {
6768
+ const withoutWildcard = stripLeadingTargetWildcard(value);
6769
+ const slashIndex = withoutWildcard.indexOf("/");
6770
+ if (slashIndex <= 0) {
6771
+ return undefined;
6772
+ }
6773
+ const host = withoutWildcard.slice(0, slashIndex).replace(/^www\./, "");
6774
+ return host.includes(".") || host === "localhost" ? host : undefined;
6775
+ }
6776
+ function targetPathVariants(value) {
6777
+ const withoutWildcard = stripLeadingTargetWildcard(value);
6778
+ const segments = withoutWildcard.split("/").filter(Boolean);
6779
+ if (segments.length <= 1) {
6780
+ return [];
6781
+ }
6782
+ const pathSegments = targetHostVariant(value) ? segments.slice(1) : segments;
6783
+ const lastSegment = pathSegments.at(-1);
6784
+ const lastWithoutExtension = lastSegment?.replace(/\.[a-z0-9]{1,12}$/i, "");
6785
+ return uniqueStrings([
6786
+ pathSegments.join("/"),
6787
+ lastSegment,
6788
+ lastWithoutExtension && lastWithoutExtension !== lastSegment ? lastWithoutExtension : undefined
6789
+ ].filter((entry) => entry !== undefined && entry.length >= 2));
5900
6790
  }
5901
6791
  function wildcardTargetMatch(pattern, value) {
5902
6792
  if (!pattern.includes("*")) {