@@ -198,6 +198,41 @@ def read_events_per_file(
198198 return {fp : ev for fp , (_ , ev ) in best .items ()}
199199
200200
201+ def read_intent_events (
202+ path : str ,
203+ min_ts : int ,
204+ ) -> List [dict ]:
205+ """Read intent events from session.jsonl (type=intent, written by set_intent MCP tool).
206+
207+ Returns all matching intent events sorted by timestamp (most recent last).
208+ """
209+ if not os .path .exists (path ):
210+ return []
211+ results = []
212+ try :
213+ with open (path , "r" , encoding = "utf-8" ) as f :
214+ for raw in f :
215+ line = raw .strip ()
216+ if not line :
217+ continue
218+ try :
219+ event = json .loads (line )
220+ except Exception :
221+ continue
222+ if not isinstance (event , dict ):
223+ continue
224+ if event .get ("type" ) != "intent" :
225+ continue
226+ event_ts = parse_event_ts (str (event .get ("timestamp" ) or "" ))
227+ if event_ts < min_ts :
228+ continue
229+ results .append (event )
230+ except Exception :
231+ pass
232+ results .sort (key = lambda e : parse_event_ts (str (e .get ("timestamp" ) or "" )))
233+ return results
234+
235+
201236def dominant_event (events_by_file : Dict [str , dict ], lines_by_file : Dict [str , List [Tuple [int , int ]]]) -> dict :
202237 """Pick the agent/model to use as the top-level record field.
203238
@@ -276,13 +311,17 @@ def main() -> int:
276311 if not isinstance (pending , dict ):
277312 pending = {}
278313
314+ min_ts = head_commit_ts (repo_root )
279315 events_by_file = read_events_per_file (
280316 session_log ,
281317 files_touched ,
282- head_commit_ts ( repo_root ) ,
318+ min_ts ,
283319 lines_by_file ,
284320 )
285321
322+ # Read agent-stated intent events from session.jsonl (written by set_intent MCP tool)
323+ intent_events = read_intent_events (session_log , min_ts )
324+
286325 # Top-level agent/model/session come from the dominant event (most lines written)
287326 event = dominant_event (events_by_file , lines_by_file ) or {}
288327
@@ -307,9 +346,31 @@ def main() -> int:
307346 flags = []
308347 flags = [str (f ) for f in flags ]
309348
310- intent = pending .get ("intent" )
311- if intent is not None :
312- intent = str (intent )
349+ # Intent priority: agent-stated intent event > pending context > fallback
350+ intent = None
351+ intent_type = None
352+ if intent_events :
353+ # Use the most recent intent event (prefer matching session, else latest)
354+ best_intent = None
355+ if session_id and session_id != "unknown" :
356+ for ie in reversed (intent_events ):
357+ if str (ie .get ("session_id" ) or "" ) == session_id :
358+ best_intent = ie
359+ break
360+ if not best_intent :
361+ best_intent = intent_events [- 1 ]
362+ intent = str (best_intent .get ("description" ) or "" ).strip () or None
363+ intent_type = str (best_intent .get ("intent_type" ) or "" ).strip () or None
364+
365+ if not intent :
366+ raw = pending .get ("intent" )
367+ if raw is not None :
368+ intent = str (raw ).strip () or None
369+
370+ if not intent_type :
371+ raw = pending .get ("intent_type" )
372+ if raw is not None :
373+ intent_type = str (raw ).strip () or None
313374
314375 # Per-file attribution — each file maps to the agent/model that wrote it.
315376 # Files with a session event that matches the dominant agent are omitted (finalize
@@ -364,6 +425,8 @@ def main() -> int:
364425 payload ["attribution" ] = attribution
365426 if intent :
366427 payload ["intent" ] = intent
428+ if intent_type :
429+ payload ["intent_type" ] = intent_type
367430 if trust is not None :
368431 payload ["trust" ] = trust
369432
0 commit comments