@metabob/minibob 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/CHANGELOG.md +112 -0
  3. package/README.md +380 -0
  4. package/bin/minibob.js +36 -0
  5. package/dist/acp-gossip.d.ts +72 -0
  6. package/dist/acp-gossip.d.ts.map +1 -0
  7. package/dist/acp-gossip.js +156 -0
  8. package/dist/acp-gossip.js.map +1 -0
  9. package/dist/acp.d.ts +62 -0
  10. package/dist/acp.d.ts.map +1 -0
  11. package/dist/acp.js +292 -0
  12. package/dist/acp.js.map +1 -0
  13. package/dist/activity.d.ts +157 -0
  14. package/dist/activity.d.ts.map +1 -0
  15. package/dist/activity.js +518 -0
  16. package/dist/activity.js.map +1 -0
  17. package/dist/agent-runtime.d.ts +104 -0
  18. package/dist/agent-runtime.d.ts.map +1 -0
  19. package/dist/boredom.d.ts +125 -0
  20. package/dist/boredom.d.ts.map +1 -0
  21. package/dist/boredom.js +244 -0
  22. package/dist/boredom.js.map +1 -0
  23. package/dist/cli/acp-server.d.ts +23 -0
  24. package/dist/cli/acp-server.d.ts.map +1 -0
  25. package/dist/cli/burrow.d.ts +26 -0
  26. package/dist/cli/burrow.d.ts.map +1 -0
  27. package/dist/cli/doctor.d.ts +22 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/goal.d.ts +22 -0
  30. package/dist/cli/goal.d.ts.map +1 -0
  31. package/dist/cli/index.d.ts +47 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/instance-registry.d.ts +78 -0
  34. package/dist/cli/instance-registry.d.ts.map +1 -0
  35. package/dist/cli/observe.d.ts +35 -0
  36. package/dist/cli/observe.d.ts.map +1 -0
  37. package/dist/cli/vessel.d.ts +14 -0
  38. package/dist/cli/vessel.d.ts.map +1 -0
  39. package/dist/composition-observer.d.ts +96 -0
  40. package/dist/composition-observer.d.ts.map +1 -0
  41. package/dist/config.d.ts +36 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +128 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/docker/Dockerfile +35 -0
  46. package/dist/environment.d.ts +72 -0
  47. package/dist/environment.d.ts.map +1 -0
  48. package/dist/environment.js +142 -0
  49. package/dist/environment.js.map +1 -0
  50. package/dist/goal-processor.d.ts +165 -0
  51. package/dist/goal-processor.d.ts.map +1 -0
  52. package/dist/helm/minibob-cluster/Chart.yaml +13 -0
  53. package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
  54. package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
  55. package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
  56. package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
  57. package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
  58. package/dist/helm/minibob-cluster/values-local.yaml +41 -0
  59. package/dist/helm/minibob-cluster/values-production.yaml +57 -0
  60. package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
  61. package/dist/helm/minibob-cluster/values.yaml +127 -0
  62. package/dist/improviser.d.ts +74 -0
  63. package/dist/improviser.d.ts.map +1 -0
  64. package/dist/impulse-filter.d.ts +74 -0
  65. package/dist/impulse-filter.d.ts.map +1 -0
  66. package/dist/impulse.d.ts +92 -0
  67. package/dist/impulse.d.ts.map +1 -0
  68. package/dist/impulse.js +234 -0
  69. package/dist/impulse.js.map +1 -0
  70. package/dist/lib.d.ts +29 -0
  71. package/dist/lib.d.ts.map +1 -0
  72. package/dist/lib.js +18561 -0
  73. package/dist/lib.js.map +98 -0
  74. package/dist/lifecycle-hooks.d.ts +99 -0
  75. package/dist/lifecycle-hooks.d.ts.map +1 -0
  76. package/dist/lifecycle-hooks.js +135 -0
  77. package/dist/lifecycle-hooks.js.map +1 -0
  78. package/dist/llm.d.ts +31 -0
  79. package/dist/llm.d.ts.map +1 -0
  80. package/dist/llm.js +349 -0
  81. package/dist/llm.js.map +1 -0
  82. package/dist/mcp-activity-bridge.d.ts +66 -0
  83. package/dist/mcp-activity-bridge.d.ts.map +1 -0
  84. package/dist/mcp-activity-bridge.js +126 -0
  85. package/dist/mcp-activity-bridge.js.map +1 -0
  86. package/dist/mcp.d.ts +216 -0
  87. package/dist/mcp.d.ts.map +1 -0
  88. package/dist/mcp.js +292 -0
  89. package/dist/mcp.js.map +1 -0
  90. package/dist/memory-agent.d.ts +92 -0
  91. package/dist/memory-agent.d.ts.map +1 -0
  92. package/dist/memory-agent.js +277 -0
  93. package/dist/memory-agent.js.map +1 -0
  94. package/dist/runtime-mapping.d.ts +97 -0
  95. package/dist/runtime-mapping.d.ts.map +1 -0
  96. package/dist/search-first-executor.d.ts +113 -0
  97. package/dist/search-first-executor.d.ts.map +1 -0
  98. package/dist/session.d.ts +48 -0
  99. package/dist/session.d.ts.map +1 -0
  100. package/dist/template-extractor.d.ts +9 -0
  101. package/dist/template-extractor.d.ts.map +1 -0
  102. package/dist/template-generator.d.ts +12 -0
  103. package/dist/template-generator.d.ts.map +1 -0
  104. package/dist/tools.d.ts +58 -0
  105. package/dist/tools.d.ts.map +1 -0
  106. package/dist/tools.js +771 -0
  107. package/dist/tools.js.map +1 -0
  108. package/dist/types.d.ts +503 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +8 -0
  111. package/dist/types.js.map +1 -0
  112. package/dist/understanding/analyzer.d.ts +55 -0
  113. package/dist/understanding/analyzer.d.ts.map +1 -0
  114. package/dist/understanding/explorer.d.ts +73 -0
  115. package/dist/understanding/explorer.d.ts.map +1 -0
  116. package/dist/understanding/index.d.ts +7 -0
  117. package/dist/understanding/index.d.ts.map +1 -0
  118. package/dist/understanding/types.d.ts +136 -0
  119. package/dist/understanding/types.d.ts.map +1 -0
  120. package/dist/validation.d.ts +29 -0
  121. package/dist/validation.d.ts.map +1 -0
  122. package/dist/validation.js +106 -0
  123. package/dist/validation.js.map +1 -0
  124. package/dist/vessel-bootstrap.d.ts +190 -0
  125. package/dist/vessel-bootstrap.d.ts.map +1 -0
  126. package/dist/vessel-registry.d.ts +229 -0
  127. package/dist/vessel-registry.d.ts.map +1 -0
  128. package/index.ts +1329 -0
  129. package/package.json +54 -0
  130. package/src/acp-gossip.ts +193 -0
  131. package/src/acp.ts +362 -0
  132. package/src/activity.ts +1464 -0
  133. package/src/agent-runtime.ts +365 -0
  134. package/src/boredom.ts +423 -0
  135. package/src/cli/acp-server.ts +377 -0
  136. package/src/cli/burrow.ts +896 -0
  137. package/src/cli/doctor.ts +526 -0
  138. package/src/cli/goal.ts +224 -0
  139. package/src/cli/index.ts +147 -0
  140. package/src/cli/instance-registry.ts +271 -0
  141. package/src/cli/observe.ts +682 -0
  142. package/src/cli/vessel.ts +287 -0
  143. package/src/components/SystemOverview.tsx +331 -0
  144. package/src/composition-observer.ts +449 -0
  145. package/src/config.ts +172 -0
  146. package/src/environment.ts +167 -0
  147. package/src/goal-processor.ts +654 -0
  148. package/src/improviser.ts +591 -0
  149. package/src/impulse-filter.ts +273 -0
  150. package/src/impulse.ts +311 -0
  151. package/src/lib.ts +147 -0
  152. package/src/lifecycle-hooks.ts +181 -0
  153. package/src/llm.ts +434 -0
  154. package/src/mcp-activity-bridge.ts +158 -0
  155. package/src/mcp.ts +747 -0
  156. package/src/memory-agent.ts +316 -0
  157. package/src/runtime-mapping.ts +527 -0
  158. package/src/search-first-executor.ts +666 -0
  159. package/src/session.ts +141 -0
  160. package/src/template-extractor.ts +256 -0
  161. package/src/template-generator.ts +130 -0
  162. package/src/tools.ts +924 -0
  163. package/src/types.ts +497 -0
  164. package/src/understanding/analyzer.ts +354 -0
  165. package/src/understanding/explorer.ts +488 -0
  166. package/src/understanding/index.ts +27 -0
  167. package/src/understanding/types.ts +153 -0
  168. package/src/validation.ts +125 -0
  169. package/src/vessel-bootstrap.ts +440 -0
  170. package/src/vessel-registry.ts +621 -0
  171. package/templates/core/edit-file.json +85 -0
  172. package/templates/understanding/diagnose-problem.json +32 -0
  173. package/templates/understanding/explore-codebase-v2.json +57 -0
  174. package/templates/understanding/explore-codebase.json +37 -0
@@ -0,0 +1,621 @@
1
+ /**
2
+ * Vessel Registry
3
+ *
4
+ * Tracks codebases that have been burrowed into and converted to vessels.
5
+ * Each vessel has:
6
+ * - Unique identity
7
+ * - Learned activities (from execution traces)
8
+ * - Defined impulses (context data)
9
+ * - Registered hooks (lifecycle events)
10
+ * - Provided tools (capabilities)
11
+ * - Composition graph (how activities relate)
12
+ *
13
+ * The registry enables:
14
+ * - Discovery: Find vessels available for use
15
+ * - Selection: Choose which vessel's activities to use
16
+ * - Learning: Track activity performance per vessel
17
+ * - Development: Debug and evolve vessel capabilities
18
+ */
19
+
20
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
21
+ import { join, resolve } from 'path'
22
+
23
+ // Backend API endpoint for vessel registry sync
24
+ const BACKEND_ENDPOINT = process.env.MINIBOB_MCP_ENDPOINT || 'http://api.minibob.local'
25
+
26
+ // =============================================================================
27
+ // TYPES
28
+ // =============================================================================
29
+
30
+ export interface VesselIdentity {
31
+ /** Unique vessel ID */
32
+ id: string
33
+ /** Human-readable name */
34
+ name: string
35
+ /** Path to the codebase */
36
+ path: string
37
+ /** When the vessel was created (burrowed into) */
38
+ createdAt: number
39
+ /** Last time the vessel was active */
40
+ lastActiveAt: number
41
+ /** Version (from package.json or inferred) */
42
+ version?: string
43
+ /** Description */
44
+ description?: string
45
+ }
46
+
47
+ export interface VesselActivity {
48
+ /** Activity ID */
49
+ id: string
50
+ /** Activity name */
51
+ name: string
52
+ /** How the activity was created */
53
+ origin: 'extracted' | 'defined' | 'improvised'
54
+ /** Source location in code (if known) */
55
+ sourceLocation?: {
56
+ file: string
57
+ line: number
58
+ function?: string
59
+ }
60
+ /** Execution count */
61
+ executions: number
62
+ /** Success rate */
63
+ successRate: number
64
+ /** Average duration (ms) */
65
+ avgDuration: number
66
+ /** Thompson Sampling parameters */
67
+ thompson: {
68
+ alpha: number
69
+ beta: number
70
+ }
71
+ }
72
+
73
+ export interface VesselImpulse {
74
+ /** Impulse ID */
75
+ id: string
76
+ /** Impulse type */
77
+ type: 'config' | 'context' | 'feature-flag' | 'mock' | 'override'
78
+ /** Description */
79
+ description?: string
80
+ /** Default value */
81
+ defaultValue?: unknown
82
+ /** Current value (if set) */
83
+ currentValue?: unknown
84
+ }
85
+
86
+ export interface VesselHook {
87
+ /** Hook ID */
88
+ id: string
89
+ /** Hook type */
90
+ type: 'startup' | 'shutdown' | 'error' | 'activity.before' | 'activity.after' | 'custom'
91
+ /** Handler location */
92
+ handler: string
93
+ /** Whether the hook is enabled */
94
+ enabled: boolean
95
+ }
96
+
97
+ export interface VesselTool {
98
+ /** Tool name */
99
+ name: string
100
+ /** Tool description */
101
+ description: string
102
+ /** Input schema */
103
+ inputSchema?: Record<string, unknown>
104
+ /** How to invoke the tool */
105
+ invocation: {
106
+ type: 'function' | 'command' | 'http' | 'mcp'
107
+ target: string
108
+ }
109
+ }
110
+
111
+ export interface VesselComposition {
112
+ /** Activity compositions recorded */
113
+ compositions: Array<{
114
+ parent: string
115
+ child: string
116
+ count: number
117
+ successRate: number
118
+ }>
119
+ /** Inferred activity sequences */
120
+ sequences: Array<{
121
+ activities: string[]
122
+ count: number
123
+ avgDuration: number
124
+ }>
125
+ }
126
+
127
+ export interface Vessel extends VesselIdentity {
128
+ /** Activities available in this vessel */
129
+ activities: VesselActivity[]
130
+ /** Impulses defined for this vessel */
131
+ impulses: VesselImpulse[]
132
+ /** Hooks registered for this vessel */
133
+ hooks: VesselHook[]
134
+ /** Tools provided by this vessel */
135
+ tools: VesselTool[]
136
+ /** Activity composition data */
137
+ composition: VesselComposition
138
+ /** Runtime mapping data */
139
+ mapping: RuntimeMapping
140
+ }
141
+
142
+ // =============================================================================
143
+ // RUNTIME MAPPING
144
+ // =============================================================================
145
+
146
+ /**
147
+ * Maps runtime behavior to source code and intent
148
+ */
149
+ export interface RuntimeMapping {
150
+ /** Function call → source location */
151
+ functionMap: Map<string, SourceLocation>
152
+ /** Source location → inferred intent */
153
+ intentMap: Map<string, InferredIntent>
154
+ /** Execution patterns → activities */
155
+ patternMap: Map<string, string>
156
+ }
157
+
158
+ export interface SourceLocation {
159
+ file: string
160
+ line: number
161
+ column?: number
162
+ functionName?: string
163
+ className?: string
164
+ moduleName?: string
165
+ }
166
+
167
+ export interface InferredIntent {
168
+ /** What the code does */
169
+ purpose: string
170
+ /** Why it exists */
171
+ rationale?: string
172
+ /** Expected inputs */
173
+ inputs?: string[]
174
+ /** Expected outputs */
175
+ outputs?: string[]
176
+ /** Constraints */
177
+ constraints?: string[]
178
+ /** Success criteria */
179
+ successCriteria?: string[]
180
+ /** Confidence in this inference */
181
+ confidence: number
182
+ /** How the intent was inferred */
183
+ source: 'function-name' | 'comment' | 'docstring' | 'variable-names' | 'data-flow' | 'llm-analysis'
184
+ }
185
+
186
+ // =============================================================================
187
+ // REGISTRY STORAGE
188
+ // =============================================================================
189
+
190
+ const REGISTRY_DIR = '.minibob/registry'
191
+ const VESSELS_FILE = 'vessels.json'
192
+
193
+ interface RegistryData {
194
+ version: string
195
+ vessels: Record<string, Omit<Vessel, 'mapping'> & { mapping?: any }>
196
+ lastUpdated: number
197
+ }
198
+
199
+ function getRegistryPath(): string {
200
+ // Use home directory for global registry
201
+ const home = process.env.HOME || process.env.USERPROFILE || '.'
202
+ return join(home, REGISTRY_DIR)
203
+ }
204
+
205
+ function ensureRegistryDir(): void {
206
+ const dir = getRegistryPath()
207
+ if (!existsSync(dir)) {
208
+ mkdirSync(dir, { recursive: true })
209
+ }
210
+ }
211
+
212
+ function loadRegistry(): RegistryData {
213
+ ensureRegistryDir()
214
+ const filePath = join(getRegistryPath(), VESSELS_FILE)
215
+
216
+ if (!existsSync(filePath)) {
217
+ return {
218
+ version: '1.0.0',
219
+ vessels: {},
220
+ lastUpdated: Date.now(),
221
+ }
222
+ }
223
+
224
+ try {
225
+ const data = readFileSync(filePath, 'utf-8')
226
+ return JSON.parse(data)
227
+ } catch (error) {
228
+ console.warn('[VesselRegistry] Failed to load registry, starting fresh')
229
+ return {
230
+ version: '1.0.0',
231
+ vessels: {},
232
+ lastUpdated: Date.now(),
233
+ }
234
+ }
235
+ }
236
+
237
+ function saveRegistry(data: RegistryData): void {
238
+ ensureRegistryDir()
239
+ const filePath = join(getRegistryPath(), VESSELS_FILE)
240
+ data.lastUpdated = Date.now()
241
+ writeFileSync(filePath, JSON.stringify(data, null, 2))
242
+ }
243
+
244
+ // =============================================================================
245
+ // BACKEND SYNC
246
+ // =============================================================================
247
+
248
+ /**
249
+ * Sync vessel registration with backend
250
+ */
251
+ async function syncVesselToBackend(vessel: Vessel, functionMappings?: Array<{
252
+ functionName: string
253
+ file: string
254
+ line: number
255
+ className?: string
256
+ intent?: InferredIntent
257
+ }>): Promise<boolean> {
258
+ try {
259
+ const payload = {
260
+ vessel_id: vessel.id,
261
+ name: vessel.name,
262
+ path: vessel.path,
263
+ description: vessel.description,
264
+ version: vessel.version,
265
+ framework: (vessel as any).framework,
266
+ activities: vessel.activities,
267
+ impulses: vessel.impulses,
268
+ hooks: vessel.hooks,
269
+ tools: vessel.tools,
270
+ composition: vessel.composition,
271
+ functionMappings: functionMappings || [],
272
+ created_at: new Date(vessel.createdAt).toISOString(),
273
+ }
274
+
275
+ const response = await fetch(`${BACKEND_ENDPOINT}/v2/vessels/codebase/register`, {
276
+ method: 'POST',
277
+ headers: { 'Content-Type': 'application/json' },
278
+ body: JSON.stringify(payload),
279
+ })
280
+
281
+ if (!response.ok) {
282
+ const text = await response.text()
283
+ console.warn(`[VesselRegistry] Backend sync failed: ${response.status} ${text}`)
284
+ return false
285
+ }
286
+
287
+ const result = await response.json()
288
+ console.log(`[VesselRegistry] Synced to backend: ${vessel.name} (${result.stored?.functionMappings || 0} mappings)`)
289
+ return true
290
+
291
+ } catch (error) {
292
+ console.warn(`[VesselRegistry] Backend sync failed:`, error instanceof Error ? error.message : error)
293
+ return false
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Record activity execution to backend (for Thompson Sampling)
299
+ */
300
+ export async function recordActivityExecution(
301
+ vesselId: string,
302
+ activityId: string,
303
+ success: boolean,
304
+ durationMs: number
305
+ ): Promise<boolean> {
306
+ try {
307
+ const response = await fetch(
308
+ `${BACKEND_ENDPOINT}/v2/vessels/registry/${vesselId}/activities/${activityId}/execution`,
309
+ {
310
+ method: 'POST',
311
+ headers: { 'Content-Type': 'application/json' },
312
+ body: JSON.stringify({ success, duration_ms: durationMs }),
313
+ }
314
+ )
315
+
316
+ if (!response.ok) {
317
+ console.warn(`[VesselRegistry] Failed to record execution: ${response.status}`)
318
+ return false
319
+ }
320
+
321
+ return true
322
+ } catch (error) {
323
+ console.warn(`[VesselRegistry] Failed to record execution:`, error instanceof Error ? error.message : error)
324
+ return false
325
+ }
326
+ }
327
+
328
+ // =============================================================================
329
+ // REGISTRY OPERATIONS
330
+ // =============================================================================
331
+
332
+ /**
333
+ * Register a new vessel (after burrowing into a codebase)
334
+ */
335
+ export function registerVessel(vessel: Omit<Vessel, 'id' | 'createdAt' | 'lastActiveAt' | 'mapping'>): Vessel {
336
+ const registry = loadRegistry()
337
+
338
+ // Generate ID from path
339
+ const normalizedPath = resolve(vessel.path)
340
+ const id = `vessel_${Buffer.from(normalizedPath).toString('base64url').slice(0, 16)}`
341
+
342
+ // Check if already registered
343
+ if (registry.vessels[id]) {
344
+ console.log(`[VesselRegistry] Vessel already registered: ${id}`)
345
+ return registry.vessels[id] as any
346
+ }
347
+
348
+ const now = Date.now()
349
+ const fullVessel: Vessel = {
350
+ ...vessel,
351
+ id,
352
+ createdAt: now,
353
+ lastActiveAt: now,
354
+ mapping: {
355
+ functionMap: new Map(),
356
+ intentMap: new Map(),
357
+ patternMap: new Map(),
358
+ },
359
+ }
360
+
361
+ // Store without Map objects (they don't serialize)
362
+ registry.vessels[id] = {
363
+ ...fullVessel,
364
+ mapping: {
365
+ functionMap: {},
366
+ intentMap: {},
367
+ patternMap: {},
368
+ },
369
+ }
370
+
371
+ saveRegistry(registry)
372
+ console.log(`[VesselRegistry] Registered vessel: ${vessel.name} (${id})`)
373
+
374
+ return fullVessel
375
+ }
376
+
377
+ /**
378
+ * Get a vessel by ID
379
+ */
380
+ export function getVessel(id: string): Vessel | null {
381
+ const registry = loadRegistry()
382
+ const vessel = registry.vessels[id]
383
+
384
+ if (!vessel) return null
385
+
386
+ // Reconstitute Map objects
387
+ return {
388
+ ...vessel,
389
+ mapping: {
390
+ functionMap: new Map(Object.entries(vessel.mapping?.functionMap || {})),
391
+ intentMap: new Map(Object.entries(vessel.mapping?.intentMap || {})),
392
+ patternMap: new Map(Object.entries(vessel.mapping?.patternMap || {})),
393
+ },
394
+ } as Vessel
395
+ }
396
+
397
+ /**
398
+ * Get vessel by path
399
+ */
400
+ export function getVesselByPath(path: string): Vessel | null {
401
+ const normalizedPath = resolve(path)
402
+ const registry = loadRegistry()
403
+
404
+ for (const vessel of Object.values(registry.vessels)) {
405
+ if (resolve(vessel.path) === normalizedPath) {
406
+ return getVessel(vessel.id)
407
+ }
408
+ }
409
+
410
+ return null
411
+ }
412
+
413
+ /**
414
+ * List all registered vessels
415
+ */
416
+ export function listVessels(): VesselIdentity[] {
417
+ const registry = loadRegistry()
418
+ return Object.values(registry.vessels).map(v => ({
419
+ id: v.id,
420
+ name: v.name,
421
+ path: v.path,
422
+ createdAt: v.createdAt,
423
+ lastActiveAt: v.lastActiveAt,
424
+ version: v.version,
425
+ description: v.description,
426
+ }))
427
+ }
428
+
429
+ /**
430
+ * Update vessel activity data
431
+ */
432
+ export function updateVesselActivities(vesselId: string, activities: VesselActivity[]): void {
433
+ const registry = loadRegistry()
434
+
435
+ if (!registry.vessels[vesselId]) {
436
+ throw new Error(`Vessel not found: ${vesselId}`)
437
+ }
438
+
439
+ registry.vessels[vesselId].activities = activities
440
+ registry.vessels[vesselId].lastActiveAt = Date.now()
441
+ saveRegistry(registry)
442
+ }
443
+
444
+ /**
445
+ * Add an activity to a vessel
446
+ */
447
+ export function addVesselActivity(vesselId: string, activity: VesselActivity): void {
448
+ const registry = loadRegistry()
449
+
450
+ if (!registry.vessels[vesselId]) {
451
+ throw new Error(`Vessel not found: ${vesselId}`)
452
+ }
453
+
454
+ const existing = registry.vessels[vesselId].activities.findIndex(a => a.id === activity.id)
455
+ if (existing >= 0) {
456
+ registry.vessels[vesselId].activities[existing] = activity
457
+ } else {
458
+ registry.vessels[vesselId].activities.push(activity)
459
+ }
460
+
461
+ registry.vessels[vesselId].lastActiveAt = Date.now()
462
+ saveRegistry(registry)
463
+ }
464
+
465
+ /**
466
+ * Update runtime mapping for a vessel
467
+ */
468
+ export function updateVesselMapping(
469
+ vesselId: string,
470
+ type: 'function' | 'intent' | 'pattern',
471
+ key: string,
472
+ value: SourceLocation | InferredIntent | string
473
+ ): void {
474
+ const registry = loadRegistry()
475
+
476
+ if (!registry.vessels[vesselId]) {
477
+ throw new Error(`Vessel not found: ${vesselId}`)
478
+ }
479
+
480
+ if (!registry.vessels[vesselId].mapping) {
481
+ registry.vessels[vesselId].mapping = {
482
+ functionMap: {},
483
+ intentMap: {},
484
+ patternMap: {},
485
+ }
486
+ }
487
+
488
+ switch (type) {
489
+ case 'function':
490
+ registry.vessels[vesselId].mapping.functionMap[key] = value as SourceLocation
491
+ break
492
+ case 'intent':
493
+ registry.vessels[vesselId].mapping.intentMap[key] = value as InferredIntent
494
+ break
495
+ case 'pattern':
496
+ registry.vessels[vesselId].mapping.patternMap[key] = value as string
497
+ break
498
+ }
499
+
500
+ saveRegistry(registry)
501
+ }
502
+
503
+ /**
504
+ * Record activity composition for a vessel
505
+ */
506
+ export function recordVesselComposition(
507
+ vesselId: string,
508
+ parent: string,
509
+ child: string,
510
+ success: boolean
511
+ ): void {
512
+ const registry = loadRegistry()
513
+
514
+ if (!registry.vessels[vesselId]) {
515
+ throw new Error(`Vessel not found: ${vesselId}`)
516
+ }
517
+
518
+ const compositions = registry.vessels[vesselId].composition?.compositions || []
519
+ const existing = compositions.find(c => c.parent === parent && c.child === child)
520
+
521
+ if (existing) {
522
+ existing.count++
523
+ // Update success rate
524
+ const successCount = Math.round(existing.successRate * (existing.count - 1) / 100)
525
+ existing.successRate = ((successCount + (success ? 1 : 0)) / existing.count) * 100
526
+ } else {
527
+ compositions.push({
528
+ parent,
529
+ child,
530
+ count: 1,
531
+ successRate: success ? 100 : 0,
532
+ })
533
+ }
534
+
535
+ registry.vessels[vesselId].composition = {
536
+ ...registry.vessels[vesselId].composition,
537
+ compositions,
538
+ }
539
+
540
+ saveRegistry(registry)
541
+ }
542
+
543
+ /**
544
+ * Deregister a vessel
545
+ */
546
+ export function deregisterVessel(id: string): boolean {
547
+ const registry = loadRegistry()
548
+
549
+ if (!registry.vessels[id]) {
550
+ return false
551
+ }
552
+
553
+ delete registry.vessels[id]
554
+ saveRegistry(registry)
555
+ console.log(`[VesselRegistry] Deregistered vessel: ${id}`)
556
+
557
+ return true
558
+ }
559
+
560
+ // =============================================================================
561
+ // EXPORTS
562
+ // =============================================================================
563
+
564
+ /**
565
+ * Register a vessel and sync to backend (async version)
566
+ */
567
+ export async function registerVesselWithSync(
568
+ vessel: Omit<Vessel, 'id' | 'createdAt' | 'lastActiveAt' | 'mapping'>,
569
+ functionMappings?: Array<{
570
+ functionName: string
571
+ file: string
572
+ line: number
573
+ className?: string
574
+ intent?: InferredIntent
575
+ }>
576
+ ): Promise<Vessel> {
577
+ // Register locally first
578
+ const registeredVessel = registerVessel(vessel)
579
+
580
+ // Then sync to backend (non-blocking, but we await for reporting)
581
+ await syncVesselToBackend(registeredVessel, functionMappings)
582
+
583
+ return registeredVessel
584
+ }
585
+
586
+ /**
587
+ * Fetch vessel from backend (if local not found)
588
+ */
589
+ export async function fetchVesselFromBackend(vesselId: string): Promise<Vessel | null> {
590
+ try {
591
+ const response = await fetch(`${BACKEND_ENDPOINT}/v2/vessels/registry/${vesselId}`)
592
+ if (!response.ok) return null
593
+
594
+ const data = await response.json()
595
+ return {
596
+ ...data,
597
+ mapping: {
598
+ functionMap: new Map(),
599
+ intentMap: new Map(),
600
+ patternMap: new Map(),
601
+ },
602
+ }
603
+ } catch {
604
+ return null
605
+ }
606
+ }
607
+
608
+ export const vesselRegistry = {
609
+ register: registerVessel,
610
+ registerWithSync: registerVesselWithSync,
611
+ get: getVessel,
612
+ getByPath: getVesselByPath,
613
+ list: listVessels,
614
+ updateActivities: updateVesselActivities,
615
+ addActivity: addVesselActivity,
616
+ updateMapping: updateVesselMapping,
617
+ recordComposition: recordVesselComposition,
618
+ recordExecution: recordActivityExecution,
619
+ deregister: deregisterVessel,
620
+ fetchFromBackend: fetchVesselFromBackend,
621
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "activity_id": "core-edit-file",
3
+ "variant_id": "core-edit-file-v1",
4
+ "variant_name": "Edit File with Specific Changes",
5
+ "category": "feature",
6
+ "description": "Modify an existing file by making specific changes to its content. Use when goal mentions: change, modify, edit, update, replace values in a file.",
7
+ "scope": "global",
8
+ "task_steps": [
9
+ {
10
+ "id": "read-file",
11
+ "description": "Read the target file to understand current content",
12
+ "dependencies": [],
13
+ "subagent": "general",
14
+ "prompt": {
15
+ "template": "Read the file {{filePath}} to see its current content:\n\nread({ filePath: \"{{filePath}}\" })\n\nTake note of the current values that need to be changed.",
16
+ "maxTokens": 4096,
17
+ "compressionStrategy": "filter",
18
+ "variables": [
19
+ {
20
+ "name": "filePath",
21
+ "type": "string",
22
+ "required": true,
23
+ "description": "Path to file to edit"
24
+ }
25
+ ]
26
+ },
27
+ "retry": {
28
+ "maxAttempts": 2,
29
+ "strategy": "simple"
30
+ }
31
+ },
32
+ {
33
+ "id": "apply-changes",
34
+ "description": "Apply the specified changes using edit tool",
35
+ "dependencies": ["read-file"],
36
+ "subagent": "general",
37
+ "prompt": {
38
+ "template": "Now modify the file {{filePath}} to apply these changes:\n\n{{changes}}\n\nUse the edit tool to make ONLY the specified changes. Be precise with the oldString parameter - it must match the exact content being replaced.\n\nedit({\n filePath: \"{{filePath}}\",\n oldString: \"<exact string to replace>\",\n newString: \"<new string>\"\n})\n\nIf multiple changes are needed, make multiple edit calls.",
39
+ "maxTokens": 4096,
40
+ "compressionStrategy": "filter",
41
+ "variables": [
42
+ {
43
+ "name": "filePath",
44
+ "type": "string",
45
+ "required": true,
46
+ "description": "Path to file to edit"
47
+ },
48
+ {
49
+ "name": "changes",
50
+ "type": "string",
51
+ "required": true,
52
+ "description": "Description of what changes to make"
53
+ }
54
+ ]
55
+ },
56
+ "retry": {
57
+ "maxAttempts": 2,
58
+ "strategy": "simple"
59
+ }
60
+ },
61
+ {
62
+ "id": "verify-changes",
63
+ "description": "Read the file again to verify changes were applied",
64
+ "dependencies": ["apply-changes"],
65
+ "subagent": "general",
66
+ "prompt": {
67
+ "template": "Verify the changes by reading the file again:\n\nread({ filePath: \"{{filePath}}\" })\n\nConfirm that the requested changes have been applied correctly.",
68
+ "maxTokens": 4096,
69
+ "compressionStrategy": "filter",
70
+ "variables": [
71
+ {
72
+ "name": "filePath",
73
+ "type": "string",
74
+ "required": true,
75
+ "description": "Path to file to edit"
76
+ }
77
+ ]
78
+ },
79
+ "retry": {
80
+ "maxAttempts": 2,
81
+ "strategy": "simple"
82
+ }
83
+ }
84
+ ]
85
+ }