@@ -36,6 +36,8 @@ import { logger } from '../../core/monitoring/logger.js';
3636import { isFeatureEnabled } from '../../core/config/feature-flags.js' ;
3737import { ContentCache } from '../../core/cache/index.js' ;
3838import type { CacheStats } from '../../core/cache/index.js' ;
39+ import { TraceEventStore } from '../../core/trace/trace-event-store.js' ;
40+ import type { TraceEvent } from '../../core/trace/trace-event.js' ;
3941
4042// Linear types - imported dynamically when needed
4143type LinearTaskManager =
@@ -142,6 +144,8 @@ class LocalStackMemoryMCP {
142144 private crossSearchHandlers : CrossSearchHandlers ;
143145 private pendingPlans : Map < string , any > = new Map ( ) ;
144146 private contentCache : ContentCache ;
147+ private traceEventStore : TraceEventStore ;
148+ private sessionId : string ;
145149 private sessionTokensSaved = 0 ;
146150 private sessionCacheHits = 0 ;
147151 private sessionCacheMisses = 0 ;
@@ -198,6 +202,10 @@ class LocalStackMemoryMCP {
198202 // Initialize content-hash cache for token deduplication
199203 this . contentCache = new ContentCache ( this . db ) ;
200204
205+ // Initialize ASI-shaped trace event store
206+ this . traceEventStore = new TraceEventStore ( this . db ) ;
207+ this . sessionId = uuidv4 ( ) ;
208+
201209 // Initialize frame manager
202210 this . frameManager = new FrameManager ( this . db , this . projectId ) ;
203211
@@ -343,6 +351,54 @@ class LocalStackMemoryMCP {
343351 } ;
344352 }
345353
354+ // ------------------------------------------------------------------
355+ // Trace event handlers
356+ // ------------------------------------------------------------------
357+
358+ private handleTraceEvents ( args : Record < string , unknown > ) {
359+ const events = this . traceEventStore . query ( {
360+ session_id : args . session_id as string | undefined ,
361+ operation : args . operation as string | undefined ,
362+ min_score : args . min_score as number | undefined ,
363+ has_feedback : args . has_feedback as boolean | undefined ,
364+ limit : ( args . limit as number ) ?? 50 ,
365+ } ) ;
366+ return {
367+ content : [ { type : 'text' , text : JSON . stringify ( events ) } ] ,
368+ isError : false ,
369+ } ;
370+ }
371+
372+ private handleTraceEventStats ( args : Record < string , unknown > ) {
373+ const stats = this . traceEventStore . getStats ( {
374+ session_id : args . session_id as string | undefined ,
375+ } ) ;
376+ return {
377+ content : [ { type : 'text' , text : JSON . stringify ( stats ) } ] ,
378+ isError : false ,
379+ } ;
380+ }
381+
382+ private handleTraceEventAnnotate ( args : Record < string , unknown > ) {
383+ const id = String ( args . id ?? '' ) ;
384+ if ( ! id ) {
385+ return {
386+ content : [
387+ { type : 'text' , text : JSON . stringify ( { error : 'id is required' } ) } ,
388+ ] ,
389+ isError : true ,
390+ } ;
391+ }
392+ const ok = this . traceEventStore . annotate ( id , {
393+ score : args . score as number | undefined ,
394+ feedback : args . feedback as string | undefined ,
395+ } ) ;
396+ return {
397+ content : [ { type : 'text' , text : JSON . stringify ( { ok, id } ) } ] ,
398+ isError : false ,
399+ } ;
400+ }
401+
346402 private findProjectRoot ( ) : string {
347403 let dir = process . cwd ( ) ;
348404 while ( dir !== '/' ) {
@@ -571,6 +627,12 @@ class LocalStackMemoryMCP {
571627 description : 'Which agent implements code' ,
572628 } ,
573629 maxIters : { type : 'number' , default : 2 } ,
630+ verificationCommands : {
631+ type : 'array' ,
632+ items : { type : 'string' } ,
633+ description :
634+ 'Optional repro/test commands that must pass after implementation' ,
635+ } ,
574636 recordFrame : { type : 'boolean' , default : true } ,
575637 execute : { type : 'boolean' , default : true } ,
576638 } ,
@@ -1424,6 +1486,50 @@ class LocalStackMemoryMCP {
14241486 required : [ 'content' ] ,
14251487 } ,
14261488 } ,
1489+ // Trace event tools
1490+ {
1491+ name : 'trace_events' ,
1492+ description :
1493+ 'Query ASI-shaped trace events. Filter by session, operation, min score, or feedback presence. Returns events with provenance, cost, and token data.' ,
1494+ inputSchema : {
1495+ type : 'object' ,
1496+ properties : {
1497+ session_id : { type : 'string' } ,
1498+ operation : { type : 'string' } ,
1499+ min_score : { type : 'number' } ,
1500+ has_feedback : { type : 'boolean' } ,
1501+ limit : { type : 'number' } ,
1502+ } ,
1503+ } ,
1504+ } ,
1505+ {
1506+ name : 'trace_event_stats' ,
1507+ description :
1508+ 'Get aggregate trace event statistics: total tokens, cost, operation counts, host distribution.' ,
1509+ inputSchema : {
1510+ type : 'object' ,
1511+ properties : {
1512+ session_id : { type : 'string' } ,
1513+ } ,
1514+ } ,
1515+ } ,
1516+ {
1517+ name : 'trace_event_annotate' ,
1518+ description :
1519+ 'Add a numeric score and/or textual feedback to a trace event. Used by GEPA-class optimizers.' ,
1520+ inputSchema : {
1521+ type : 'object' ,
1522+ properties : {
1523+ id : { type : 'string' , description : 'Trace event ID' } ,
1524+ score : { type : 'number' , description : 'Numeric score (0-1)' } ,
1525+ feedback : {
1526+ type : 'string' ,
1527+ description : 'Textual ASI feedback' ,
1528+ } ,
1529+ } ,
1530+ required : [ 'id' ] ,
1531+ } ,
1532+ } ,
14271533 ] ,
14281534 } ;
14291535 }
@@ -1791,6 +1897,19 @@ class LocalStackMemoryMCP {
17911897 result = this . handleCacheLookup ( args ) ;
17921898 break ;
17931899
1900+ // Trace event tools
1901+ case 'trace_events' :
1902+ result = this . handleTraceEvents ( args ) ;
1903+ break ;
1904+
1905+ case 'trace_event_stats' :
1906+ result = this . handleTraceEventStats ( args ) ;
1907+ break ;
1908+
1909+ case 'trace_event_annotate' :
1910+ result = this . handleTraceEventAnnotate ( args ) ;
1911+ break ;
1912+
17941913 default :
17951914 throw new Error ( `Unknown tool: ${ name } ` ) ;
17961915 }
@@ -1843,13 +1962,58 @@ class LocalStackMemoryMCP {
18431962
18441963 // Add to trace detector
18451964 this . traceDetector . addToolCall ( toolCall ) ;
1965+
1966+ // --- Record ASI-shaped trace event ---
1967+ try {
1968+ const traceEvent : TraceEvent = {
1969+ timestamp : new Date ( startTime ) . toISOString ( ) ,
1970+ session_id : this . sessionId ,
1971+ trace_id : callId ,
1972+ tenant_id : 'local' ,
1973+ actor : {
1974+ host : process . env [ 'STACKMEMORY_HOST' ] || 'claude-code' ,
1975+ agent : 'stackmemory-mcp' ,
1976+ user : process . env [ 'USER' ] || 'anonymous' ,
1977+ } ,
1978+ operation : name ,
1979+ inputs : args as Record < string , unknown> ,
1980+ outputs : error
1981+ ? { error : error . message }
1982+ : ( ( result as Record < string , unknown > ) ?? { } ) ,
1983+ tokens_in : 0 ,
1984+ tokens_out : 0 ,
1985+ cost_usd : 0 ,
1986+ duration_ms : endTime - startTime ,
1987+ error : error ?. message ,
1988+ provenance : {
1989+ sources : [ { type : 'tool' , id : name } ] ,
1990+ derivation : [ 'mcp-call' ] ,
1991+ confidence : 1.0 ,
1992+ } ,
1993+ } ;
1994+ this . traceEventStore . record ( traceEvent ) ;
1995+ } catch {
1996+ // Trace recording is non-fatal
1997+ }
18461998 }
18471999
18482000 return result ;
18492001 }
18502002 ) ;
18512003 }
18522004
2005+ private getVerificationCommands ( args : any ) : string [ ] {
2006+ const commands = args . verificationCommands ?? args . verifyCommands ;
2007+ if ( Array . isArray ( commands ) ) {
2008+ return commands . map ( ( command ) => String ( command ) . trim ( ) ) . filter ( Boolean ) ;
2009+ }
2010+ const single = args . verificationCommand ?? args . verifyCommand ;
2011+ if ( typeof single === 'string' && single . trim ( ) ) {
2012+ return [ single . trim ( ) ] ;
2013+ }
2014+ return [ ] ;
2015+ }
2016+
18532017 // Handle plan_and_code tool by invoking the mm harness
18542018 private async handlePlanAndCode ( args : any ) {
18552019 const { runSpike } =
@@ -1873,6 +2037,7 @@ class LocalStackMemoryMCP {
18732037 const record = Boolean ( args . record ) ;
18742038 const recordFrame = Boolean ( args . recordFrame ) ;
18752039 const compact = Boolean ( args . compact ) ;
2040+ const verificationCommands = this . getVerificationCommands ( args ) ;
18762041
18772042 const task = String ( args . task || 'Plan and implement change' ) ;
18782043
@@ -1888,6 +2053,7 @@ class LocalStackMemoryMCP {
18882053 maxIters : isFinite ( maxIters ) ? Math . max ( 1 , maxIters ) : 2 ,
18892054 dryRun : ! execute ,
18902055 auditDir : undefined ,
2056+ verificationCommands,
18912057 recordFrame,
18922058 }
18932059 ) ;
@@ -2091,6 +2257,7 @@ class LocalStackMemoryMCP {
20912257 ) ;
20922258 const recordFrame = args . recordFrame !== false ; // default true
20932259 const execute = args . execute !== false ; // default true
2260+ const verificationCommands = this . getVerificationCommands ( args ) ;
20942261
20952262 const result = await runSpike (
20962263 { task : pending . task , repoPath : this . projectRoot } ,
@@ -2104,6 +2271,7 @@ class LocalStackMemoryMCP {
21042271 implementer : implementer === 'claude' ? 'claude' : 'codex' ,
21052272 maxIters : isFinite ( maxIters ) ? Math . max ( 1 , maxIters ) : 2 ,
21062273 dryRun : ! execute ,
2274+ verificationCommands,
21072275 recordFrame,
21082276 }
21092277 ) ;
0 commit comments