brainclaw 0.24.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ import { loadState, persistState } from '../core/state.js';
2
+ import { memoryExists } from '../core/io.js';
3
+ import { resolveTargetStore } from '../core/store-resolution.js';
4
+ import { appendAuditEntry } from '../core/audit.js';
5
+ import { resolveCurrentAgentName } from '../core/agent-registry.js';
6
+ export function runMigrate(options = {}) {
7
+ const cwd = options.cwd ?? process.cwd();
8
+ if (!memoryExists(cwd)) {
9
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
10
+ process.exit(1);
11
+ }
12
+ if (options.promoteMachineItems) {
13
+ promoteMachineItems(cwd, options.dryRun ?? false);
14
+ }
15
+ else {
16
+ console.log('Usage: brainclaw migrate --promote-machine-items [--dry-run]');
17
+ console.log('');
18
+ console.log('Moves items tagged scope:machine from project store to user store (~/.brainclaw/).');
19
+ }
20
+ }
21
+ function promoteMachineItems(cwd, dryRun) {
22
+ const state = loadState(cwd);
23
+ const agent = resolveCurrentAgentName(cwd);
24
+ const machineConstraints = state.active_constraints.filter((c) => c.scope === 'machine');
25
+ const machineDecisions = state.recent_decisions.filter((d) => d.scope === 'machine');
26
+ const machineTraps = state.known_traps.filter((t) => t.scope === 'machine');
27
+ const total = machineConstraints.length + machineDecisions.length + machineTraps.length;
28
+ if (total === 0) {
29
+ console.log('No machine-scoped items found in project store.');
30
+ return;
31
+ }
32
+ console.log(`Found ${total} machine-scoped item(s) in project store:\n`);
33
+ for (const c of machineConstraints)
34
+ console.log(` [constraint] ${c.id} — ${c.text.slice(0, 80)}`);
35
+ for (const d of machineDecisions)
36
+ console.log(` [decision] ${d.id} — ${d.text.slice(0, 80)}`);
37
+ for (const t of machineTraps)
38
+ console.log(` [trap] ${t.id} — ${t.text.slice(0, 80)}`);
39
+ if (dryRun) {
40
+ console.log(`\n(dry-run) Would move ${total} item(s) to user store.`);
41
+ return;
42
+ }
43
+ // Resolve user store
44
+ let userCwd;
45
+ try {
46
+ userCwd = resolveTargetStore(cwd, 'user');
47
+ }
48
+ catch {
49
+ console.error('Error: cannot resolve user store. Run `brainclaw setup` first.');
50
+ process.exit(1);
51
+ }
52
+ const userState = loadState(userCwd);
53
+ // Move constraints
54
+ for (const c of machineConstraints) {
55
+ userState.active_constraints.push(c);
56
+ state.active_constraints = state.active_constraints.filter((x) => x.id !== c.id);
57
+ appendAuditEntry({ actor: agent, action: 'update', item_id: c.id, item_type: 'constraint', reason: 'promote to user store (machine scope)' }, cwd);
58
+ }
59
+ // Move decisions
60
+ for (const d of machineDecisions) {
61
+ userState.recent_decisions.push(d);
62
+ state.recent_decisions = state.recent_decisions.filter((x) => x.id !== d.id);
63
+ appendAuditEntry({ actor: agent, action: 'update', item_id: d.id, item_type: 'decision', reason: 'promote to user store (machine scope)' }, cwd);
64
+ }
65
+ // Move traps
66
+ for (const t of machineTraps) {
67
+ userState.known_traps.push(t);
68
+ state.known_traps = state.known_traps.filter((x) => x.id !== t.id);
69
+ appendAuditEntry({ actor: agent, action: 'update', item_id: t.id, item_type: 'trap', reason: 'promote to user store (machine scope)' }, cwd);
70
+ }
71
+ persistState(userState, userCwd);
72
+ persistState(state, cwd);
73
+ console.log(`\n✔ Promoted ${total} item(s) to user store (${userCwd})`);
74
+ }
75
+ //# sourceMappingURL=migrate.js.map
@@ -61,6 +61,7 @@ export function createRuntimeNote(text, options, printSuccess = false) {
61
61
  host_id: hostId,
62
62
  expires_at: expiresAt,
63
63
  note_type: 'observation',
64
+ model: options.model,
64
65
  };
65
66
  saveRuntimeNote(note, options.cwd);
66
67
  const result = maybeAutoReflectRuntimeNote(note, options);
@@ -1,11 +1,10 @@
1
- import { loadState, persistState } from '../core/state.js';
2
1
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
3
2
  import { memoryExists } from '../core/io.js';
4
- import { generateIdWithLabel, nowISO } from '../core/ids.js';
5
3
  import { loadConfig } from '../core/config.js';
6
4
  import { scanText } from '../core/security.js';
7
5
  import { validateCliInput } from '../core/input-validation.js';
8
6
  import { resolveTargetStore } from '../core/store-resolution.js';
7
+ import { listTools, createTool } from '../core/registries.js';
9
8
  export function runTool(subcommand, args, options = {}) {
10
9
  const cwd = resolveTargetStore(options.cwd ?? process.cwd(), options.store ?? 'local');
11
10
  if (!memoryExists(cwd)) {
@@ -46,10 +45,7 @@ export function runTool(subcommand, args, options = {}) {
46
45
  }
47
46
  }
48
47
  function runToolList(cwd) {
49
- const state = loadState(cwd);
50
- const tools = state.recent_decisions
51
- .filter((d) => d.tags.includes('tool'))
52
- .map((d) => ({ id: d.id, name: d.text.split('\n')[0], type: d.tags.find((t) => t !== 'tool') }));
48
+ const tools = listTools(cwd);
53
49
  if (tools.length === 0) {
54
50
  console.log('No tools registered yet.');
55
51
  return;
@@ -57,9 +53,7 @@ function runToolList(cwd) {
57
53
  console.log(`\n${tools.length} tool(s):\n`);
58
54
  tools.forEach((tool) => {
59
55
  console.log(` [${tool.id}] ${tool.name}`);
60
- if (tool.type) {
61
- console.log(` type: ${tool.type}`);
62
- }
56
+ console.log(` type: ${tool.type}`);
63
57
  });
64
58
  console.log('');
65
59
  }
@@ -74,50 +68,46 @@ function runToolAdd(name, description, options, cwd) {
74
68
  process.exit(1);
75
69
  }
76
70
  }
77
- const state = loadState(cwd);
78
- const { id, short_label } = generateIdWithLabel('recent_decisions');
79
- const toolType = options.type ?? 'utility';
80
- const entry = {
81
- id,
82
- short_label,
83
- text: name,
84
- created_at: nowISO(),
71
+ const tool = createTool({
72
+ name,
73
+ description,
74
+ type: options.type,
75
+ tags: options.tag,
85
76
  author: options.author ?? resolveCurrentAgentName(cwd),
86
- tags: ['tool', toolType, ...(options.tag ?? [])],
87
- };
88
- // For now, store as decision to avoid schema migration
89
- // Will migrate to separate tool storage in v0.16
90
- state.recent_decisions.push(entry);
91
- persistState(state, cwd);
92
- console.log(`✔ Tool added: [${id}] ${name}`);
93
- console.log(' (Stored in decisions for now; will move to dedicated registry in v0.16)');
77
+ }, cwd);
78
+ console.log(`✔ Tool added: [${tool.id}] ${name} (${tool.type})`);
94
79
  }
95
80
  function runToolDescribe(toolId, cwd) {
96
- const state = loadState(cwd);
97
- const decision = state.recent_decisions.find((d) => d.id === toolId || d.short_label === toolId);
98
- if (!decision) {
81
+ const tools = listTools(cwd);
82
+ const tool = tools.find((t) => t.id === toolId || t.id.startsWith(toolId));
83
+ if (!tool) {
99
84
  console.error(`Error: tool '${toolId}' not found`);
100
85
  process.exit(1);
101
86
  }
102
- console.log(`\nTool: ${decision.text}`);
103
- console.log(`ID: ${decision.id}`);
104
- console.log(`Type: ${decision.tags.find((t) => t !== 'tool')}`);
105
- console.log(`Author: ${decision.author}`);
106
- console.log(`Created: ${decision.created_at}`);
87
+ console.log(`\nTool: ${tool.name}`);
88
+ console.log(`Description: ${tool.description}`);
89
+ console.log(`ID: ${tool.id}`);
90
+ console.log(`Type: ${tool.type}`);
91
+ console.log(`Author: ${tool.author}`);
92
+ console.log(`Created: ${tool.created_at}`);
93
+ if (tool.tags.length > 0) {
94
+ console.log(`Tags: ${tool.tags.join(', ')}`);
95
+ }
107
96
  console.log('');
108
97
  }
109
98
  function runToolSearch(query, cwd) {
110
- const state = loadState(cwd);
111
- const tools = state.recent_decisions.filter((d) => d.tags.includes('tool'));
112
- const results = tools.filter((tool) => tool.text.toLowerCase().includes(query.toLowerCase()) ||
113
- tool.tags.some((tag) => tag.toLowerCase().includes(query.toLowerCase())));
99
+ const tools = listTools(cwd);
100
+ const queryLower = query.toLowerCase();
101
+ const results = tools.filter((tool) => tool.name.toLowerCase().includes(queryLower) ||
102
+ tool.description.toLowerCase().includes(queryLower) ||
103
+ tool.tags.some((tag) => tag.toLowerCase().includes(queryLower)));
114
104
  if (results.length === 0) {
115
105
  console.log(`No tools found matching '${query}'`);
116
106
  return;
117
107
  }
118
108
  console.log(`\n${results.length} tool(s) matching '${query}':\n`);
119
- results.forEach((result) => {
120
- console.log(` [${result.id}] ${result.text.split('\n')[0]}`);
109
+ results.forEach((tool) => {
110
+ console.log(` [${tool.id}] ${tool.name} (${tool.type})`);
121
111
  });
122
112
  console.log('');
123
113
  }
@@ -441,6 +441,12 @@ function buildCommandHookEntry(command) {
441
441
  hooks: [{ type: 'command', command }],
442
442
  };
443
443
  }
444
+ function buildMatchedCommandHookEntry(matcher, command) {
445
+ return {
446
+ matcher,
447
+ hooks: [{ type: 'command', command }],
448
+ };
449
+ }
444
450
  function containsCommandHook(entries, command) {
445
451
  return entries.some((entry) => isJsonObject(entry) &&
446
452
  Array.isArray(entry.hooks) &&
@@ -582,6 +588,13 @@ export function ensureClaudeCodeSettings(cwd) {
582
588
  stopHooks.push(buildCommandHookEntry(stopCommand));
583
589
  }
584
590
  hooks.Stop = stopHooks;
591
+ // PostToolUse — check for unseen events after any brainclaw MCP tool call
592
+ const checkEventsCommand = 'npx brainclaw check-events 2>/dev/null';
593
+ const postToolHooks = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
594
+ if (!containsCommandHook(postToolHooks, checkEventsCommand)) {
595
+ postToolHooks.push(buildMatchedCommandHookEntry('mcp__brainclaw__', checkEventsCommand));
596
+ }
597
+ hooks.PostToolUse = postToolHooks;
585
598
  const { created, updated } = writeJsonFileIfChanged(filePath, {
586
599
  ...existing,
587
600
  permissions,
@@ -13,7 +13,7 @@ import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '.
13
13
  import { buildCurrentAgentResumeSummary, buildReputationRankingLookup } from './reputation.js';
14
14
  import { loadState } from './state.js';
15
15
  import { listCandidates } from './candidates.js';
16
- import { listClaims } from './claims.js';
16
+ import { listClaims, isClaimExpired } from './claims.js';
17
17
  import { listRuntimeNotes } from './runtime.js';
18
18
  import { isTrapActive, listOperationalTraps } from './traps.js';
19
19
  import { buildEstimationReport } from '../commands/estimation-report.js';
@@ -67,6 +67,7 @@ export function buildContext(options = {}) {
67
67
  score: 0,
68
68
  reasons: [],
69
69
  extra: meta.join(', '),
70
+ provenance: { actor: plan.author },
70
71
  });
71
72
  }
72
73
  for (const c of state.active_constraints) {
@@ -316,10 +317,36 @@ export function buildContext(options = {}) {
316
317
  items.splice(0, items.length, ...items.filter((i) => allowed.includes(i.section)));
317
318
  }
318
319
  const queryTerms = tokenise(target);
320
+ // Agent-layer scoring: boost items related to the current agent's claims
321
+ const agentName = agent;
322
+ const agentId = currentAgentIdentity?.agent_id;
323
+ const allClaims = [...listClaims(contextCwd), ...parentStoreClaims];
324
+ const myClaims = allClaims.filter((c) => c.status === 'active' && (agentId ? c.agent_id === agentId : c.agent === agentName));
325
+ const myClaimScopes = myClaims.map((c) => c.scope);
326
+ const otherActiveClaims = allClaims.filter((c) => c.status === 'active' && !(agentId ? c.agent_id === agentId : c.agent === agentName));
319
327
  for (const item of items) {
320
328
  const relevance = computeRelevance(item, queryTerms, profile, target);
321
329
  item.score = relevance.score;
322
330
  item.reasons = relevance.reasons;
331
+ // Layer 1: boost items in my claimed scope (+6)
332
+ if (item.score >= 0 && myClaimScopes.length > 0 && item.related_paths) {
333
+ const overlaps = item.related_paths.some((p) => myClaimScopes.some((scope) => p.includes(scope) || scope.includes(p)));
334
+ if (overlaps) {
335
+ item.score += 6;
336
+ item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my claimed scope']);
337
+ }
338
+ }
339
+ // Layer 1: boost plans assigned to me (+5)
340
+ if (item.score >= 0 && item.section === 'plan' && item.extra?.includes(`assignee:${agentName}`)) {
341
+ item.score += 5;
342
+ item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my assigned plan']);
343
+ }
344
+ // Layer 2: boost items authored by me (+2)
345
+ if (item.score >= 0 && item.provenance?.actor === agentName) {
346
+ item.score += 2;
347
+ item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my authored item']);
348
+ }
349
+ // Reputation signal
323
350
  if (item.score >= 0 && item.provenance) {
324
351
  const trustBonus = rankingLookup.getRankingBonus(item.provenance.actor_id, item.provenance.actor);
325
352
  if (trustBonus > 0) {
@@ -327,6 +354,14 @@ export function buildContext(options = {}) {
327
354
  item.reasons = uniqueReasons([...item.reasons, `reputation signal:+${trustBonus.toFixed(2)}`]);
328
355
  }
329
356
  }
357
+ // Layer 3: boost machine-scoped items for broader visibility (+1)
358
+ if (item.score >= 0) {
359
+ const itemScope = item.scope;
360
+ if (itemScope === 'machine') {
361
+ item.score += 1;
362
+ item.reasons = uniqueReasons([...item.reasons, 'machine-scope signal']);
363
+ }
364
+ }
330
365
  }
331
366
  const ranked = items
332
367
  .filter(item => item.score >= 0)
@@ -384,18 +419,15 @@ export function buildContext(options = {}) {
384
419
  ? summariseAgentTooling(rawAgentTooling)
385
420
  : undefined;
386
421
  // Build open_work: active claims and in_progress plans owned by the current agent
422
+ // Reuses myClaims computed in agent-layer scoring above
387
423
  let openWork;
388
424
  if (currentAgentIdentity || agent) {
389
- const agentName = agent;
390
- const agentId = currentAgentIdentity?.agent_id;
391
- const allClaims = [...listClaims(contextCwd), ...parentStoreClaims];
392
- const activeClaims = allClaims.filter((c) => c.status === 'active' && (agentId ? c.agent_id === agentId : c.agent === agentName));
393
- const claimPlanIds = new Set(activeClaims.map((c) => c.plan_id).filter(Boolean));
425
+ const claimPlanIds = new Set(myClaims.map((c) => c.plan_id).filter(Boolean));
394
426
  const inProgressPlans = state.plan_items.filter((p) => p.status === 'in_progress' &&
395
427
  (p.assignee === agentName || claimPlanIds.has(p.id)));
396
- if (activeClaims.length > 0 || inProgressPlans.length > 0) {
428
+ if (myClaims.length > 0 || inProgressPlans.length > 0) {
397
429
  openWork = {
398
- active_claims: activeClaims.map(({ id, scope, description, created_at, plan_id, expires_at }) => ({ id, scope, description, created_at, plan_id, expires_at })),
430
+ active_claims: myClaims.map(({ id, scope, description, created_at, plan_id, expires_at }) => ({ id, scope, description, created_at, plan_id, expires_at })),
399
431
  in_progress_plans: inProgressPlans.map(({ id, text, assignee }) => ({ id, text, assignee })),
400
432
  };
401
433
  }
@@ -471,6 +503,8 @@ export function buildContext(options = {}) {
471
503
  }
472
504
  })(),
473
505
  cross_project_items: crossProjectItems.length > 0 ? crossProjectItems : undefined,
506
+ claim_conflicts: detectClaimConflicts(myClaims, otherActiveClaims),
507
+ workflow_hints: buildWorkflowHints(myClaims, openWork, state.plan_items),
474
508
  selected,
475
509
  };
476
510
  if (options.digest) {
@@ -1232,4 +1266,64 @@ function applyCharBudget(items, maxChars) {
1232
1266
  }
1233
1267
  return selected;
1234
1268
  }
1269
+ // --- Claim conflict detection ---
1270
+ function detectClaimConflicts(myClaims, otherClaims) {
1271
+ if (myClaims.length === 0 || otherClaims.length === 0)
1272
+ return undefined;
1273
+ const conflicts = [];
1274
+ for (const mine of myClaims) {
1275
+ for (const other of otherClaims) {
1276
+ if (isClaimExpired(other))
1277
+ continue;
1278
+ const overlap = scopesOverlap(mine.scope, other.scope);
1279
+ if (overlap) {
1280
+ conflicts.push({
1281
+ my_claim_id: mine.id,
1282
+ my_scope: mine.scope,
1283
+ other_claim_id: other.id,
1284
+ other_agent: other.agent,
1285
+ other_scope: other.scope,
1286
+ overlap_reason: overlap,
1287
+ });
1288
+ }
1289
+ }
1290
+ }
1291
+ return conflicts.length > 0 ? conflicts : undefined;
1292
+ }
1293
+ function scopesOverlap(a, b) {
1294
+ const aParts = a.replace(/\\/g, '/').split(/\s+/);
1295
+ const bParts = b.replace(/\\/g, '/').split(/\s+/);
1296
+ for (const ap of aParts) {
1297
+ for (const bp of bParts) {
1298
+ if (ap === bp)
1299
+ return `exact match: ${ap}`;
1300
+ if (ap.startsWith(bp + '/') || bp.startsWith(ap + '/'))
1301
+ return `path overlap: ${ap} ↔ ${bp}`;
1302
+ }
1303
+ }
1304
+ return null;
1305
+ }
1306
+ // --- Workflow hints ---
1307
+ function buildWorkflowHints(myClaims, openWork, plans) {
1308
+ const hints = [];
1309
+ // No claims — suggest claiming before editing
1310
+ if (myClaims.length === 0) {
1311
+ const todoPlans = plans.filter((p) => p.status === 'todo' && p.priority === 'high');
1312
+ if (todoPlans.length > 0) {
1313
+ hints.push(`${todoPlans.length} high-priority plan(s) available — consider claiming one with bclaw_claim`);
1314
+ }
1315
+ }
1316
+ // Multiple unclosed claims — suggest releasing finished ones
1317
+ if (myClaims.length > 2) {
1318
+ hints.push(`You have ${myClaims.length} active claims — consider releasing finished ones with bclaw_release_claim`);
1319
+ }
1320
+ // In-progress plans without claims
1321
+ if (openWork) {
1322
+ const unclaimedInProgress = openWork.in_progress_plans.filter((p) => !openWork.active_claims.some((c) => c.plan_id === p.id));
1323
+ if (unclaimedInProgress.length > 0) {
1324
+ hints.push(`${unclaimedInProgress.length} in-progress plan(s) without a claim — consider claiming the scope you're editing`);
1325
+ }
1326
+ }
1327
+ return hints.length > 0 ? hints : undefined;
1328
+ }
1235
1329
  //# sourceMappingURL=context.js.map
@@ -81,6 +81,31 @@ export function buildCoordinationSnapshot(options = {}) {
81
81
  resolved_instructions: instructions,
82
82
  reputation_summary: reputationSummary,
83
83
  agent_reputation: agentReputation,
84
+ other_agents: buildOtherAgentsSummary(filteredClaims, filteredNotes, agent),
84
85
  };
85
86
  }
87
+ function buildOtherAgentsSummary(claims, notes, currentAgent) {
88
+ const agentMap = new Map();
89
+ for (const claim of claims) {
90
+ if (claim.agent === currentAgent)
91
+ continue;
92
+ const existing = agentMap.get(claim.agent) ?? { name: claim.agent, claim_count: 0, scopes: [] };
93
+ existing.claim_count++;
94
+ existing.scopes.push(claim.scope);
95
+ if (!existing.last_active || claim.created_at > existing.last_active) {
96
+ existing.last_active = claim.created_at;
97
+ }
98
+ agentMap.set(claim.agent, existing);
99
+ }
100
+ for (const note of notes) {
101
+ if (note.agent === currentAgent)
102
+ continue;
103
+ const existing = agentMap.get(note.agent);
104
+ if (existing && (!existing.last_active || note.created_at > existing.last_active)) {
105
+ existing.last_active = note.created_at;
106
+ }
107
+ }
108
+ const result = [...agentMap.values()];
109
+ return result.length > 0 ? result : undefined;
110
+ }
86
111
  //# sourceMappingURL=coordination.js.map
package/dist/core/io.js CHANGED
@@ -35,6 +35,8 @@ const ENTITY_DIR_MAP = {
35
35
  // discovery/ — Project entity: what's available
36
36
  'bootstrap': 'discovery/bootstrap',
37
37
  'bootstrap/seeds': 'discovery/bootstrap/seeds',
38
+ 'capabilities': 'discovery/capabilities',
39
+ 'tools': 'discovery/tools',
38
40
  // agents/ — stays at top level (already entity-aligned)
39
41
  'agents': 'agents',
40
42
  };
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import YAML from 'yaml';
4
4
  import { memoryDir, memoryPath, readFileSync, writeFileAtomic, resolveEntityDir } from './io.js';
5
- import { BootstrapApplicationReceiptSchema, BootstrapImportPlanDocumentSchema, AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, AiSurfaceTaskRequestSchema, } from './schema.js';
5
+ import { BootstrapApplicationReceiptSchema, BootstrapImportPlanDocumentSchema, AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, AiSurfaceTaskRequestSchema, ProjectCapabilitySchema, ProjectToolSchema, } from './schema.js';
6
6
  export class MigrationError extends Error {
7
7
  kind;
8
8
  documentType;
@@ -33,6 +33,8 @@ const registry = {
33
33
  runtime_note: createRegistryEntry(RuntimeNoteSchema),
34
34
  ai_surface_task: createRegistryEntry(AiSurfaceTaskRequestSchema),
35
35
  session_snapshot: createRegistryEntry(SessionSnapshotSchema),
36
+ capability: createRegistryEntry(ProjectCapabilitySchema),
37
+ tool: createRegistryEntry(ProjectToolSchema),
36
38
  trap: createRegistryEntry(TrapSchema),
37
39
  };
38
40
  function createRegistryEntry(schema) {