@@ -75,28 +75,28 @@ async def api_tasks(request: Request) -> JSONResponse:
7575 )
7676
7777 # Fetch latest Slack notification per task
78- jira_keys = [r ["jira_key" ] for r in rows ]
78+ ext_keys = [r [ "external_key" ] or r ["jira_key" ] for r in rows ]
7979 notifications = {}
80- if jira_keys :
80+ if ext_keys :
8181 notif_rows = await pool .fetch (
8282 """
83- SELECT DISTINCT ON (jira_key) jira_key , event_type, message, sent_at
83+ SELECT DISTINCT ON (external_key) external_key , event_type, message, sent_at
8484 FROM slack_notifications
85- WHERE jira_key = ANY($1)
86- ORDER BY jira_key , sent_at DESC
85+ WHERE external_key = ANY($1)
86+ ORDER BY external_key , sent_at DESC
8787 """ ,
88- jira_keys ,
88+ ext_keys ,
8989 )
9090 for nr in notif_rows :
91- notifications [nr ["jira_key " ]] = {
91+ notifications [nr ["external_key " ]] = {
9292 "event_type" : nr ["event_type" ],
9393 "message" : nr ["message" ],
9494 "sent_at" : nr ["sent_at" ].isoformat (),
9595 }
9696
9797 return JSONResponse (
9898 {
99- "items" : [_task (r , notifications .get (r ["jira_key" ])) for r in rows ],
99+ "items" : [_task (r , notifications .get (r ["external_key" ] or r [ " jira_key" ])) for r in rows ],
100100 "total" : total ,
101101 "limit" : limit ,
102102 "offset" : offset ,
@@ -105,39 +105,39 @@ async def api_tasks(request: Request) -> JSONResponse:
105105
106106
107107async def api_task_delete (request : Request ) -> JSONResponse :
108- """Archive a task by jira_key (soft delete — preserves history)."""
108+ """Archive a task by key (soft delete — preserves history)."""
109109 pool = get_pool ()
110- jira_key = request .path_params .get ("jira_key" )
111- if not jira_key :
112- return JSONResponse ({"error" : "missing jira_key " }, status_code = 400 )
110+ key = request .path_params .get ("jira_key" )
111+ if not key :
112+ return JSONResponse ({"error" : "missing key " }, status_code = 400 )
113113 row = await pool .fetchrow (
114- "UPDATE tasks SET status = 'archived'::task_status WHERE jira_key = $1 RETURNING *" ,
115- jira_key ,
114+ "UPDATE tasks SET status = 'archived'::task_status WHERE external_key = $1 RETURNING *" ,
115+ key ,
116116 )
117117 if not row :
118- return JSONResponse ({"error" : f"Task { jira_key } not found" }, status_code = 404 )
119- await bus .publish (Event ("task_archived" , {"jira_key" : jira_key }))
120- return JSONResponse ({"archived" : True , "jira_key" : jira_key , "task" : _task (row )})
118+ return JSONResponse ({"error" : f"Task { key } not found" }, status_code = 404 )
119+ await bus .publish (Event ("task_archived" , {"jira_key" : row [ " jira_key" ] or key }))
120+ return JSONResponse ({"archived" : True , "jira_key" : row [ " jira_key" ] or key , "task" : _task (row )})
121121
122122
123123async def api_task_unarchive (request : Request ) -> JSONResponse :
124- """Restore an archived task back to paused so the bot can pick it up."""
124+ """Restore an archived task back to in_progress so the bot can pick it up."""
125125 pool = get_pool ()
126- jira_key = request .path_params .get ("jira_key" )
127- if not jira_key :
128- return JSONResponse ({"error" : "missing jira_key " }, status_code = 400 )
126+ key = request .path_params .get ("jira_key" )
127+ if not key :
128+ return JSONResponse ({"error" : "missing key " }, status_code = 400 )
129129 row = await pool .fetchrow (
130- "UPDATE tasks SET status = 'in_progress'::task_status, paused_reason = NULL WHERE jira_key = $1 AND status = 'archived'::task_status RETURNING *" ,
131- jira_key ,
130+ "UPDATE tasks SET status = 'in_progress'::task_status, paused_reason = NULL WHERE external_key = $1 AND status = 'archived'::task_status RETURNING *" ,
131+ key ,
132132 )
133133 if not row :
134134 return JSONResponse (
135- {"error" : f"Task { jira_key } not found or not archived" }, status_code = 404
135+ {"error" : f"Task { key } not found or not archived" }, status_code = 404
136136 )
137137 await bus .publish (
138- Event ("task_updated" , {"jira_key" : jira_key , "status" : "in_progress" })
138+ Event ("task_updated" , {"jira_key" : row [ " jira_key" ] or key , "status" : "in_progress" })
139139 )
140- return JSONResponse ({"unarchived" : True , "jira_key" : jira_key , "task" : _task (row )})
140+ return JSONResponse ({"unarchived" : True , "jira_key" : row [ " jira_key" ] or key , "task" : _task (row )})
141141
142142
143143async def api_memories (request : Request ) -> JSONResponse :
@@ -613,9 +613,9 @@ async def api_analytics(request: Request) -> JSONResponse:
613613 SELECT
614614 CASE
615615 WHEN summary ILIKE '%%investigation%%' OR work_type = 'investigation' THEN 'investigation'
616- WHEN jira_key IS NOT NULL AND (
616+ WHEN external_key IS NOT NULL AND (
617617 summary ILIKE '%%CVE%%' OR summary ILIKE '%%cve%%'
618- OR jira_key IN (SELECT DISTINCT jira_key FROM tasks WHERE title ILIKE '%%CVE%%')
618+ OR external_key IN (SELECT DISTINCT external_key FROM tasks WHERE title ILIKE '%%CVE%%')
619619 ) THEN 'cve'
620620 WHEN work_type = 'pr_review' THEN 'pr_review'
621621 WHEN work_type = 'new_ticket' THEN 'new_ticket'
@@ -640,7 +640,7 @@ async def api_analytics(request: Request) -> JSONResponse:
640640 repo_rows = await pool .fetch (
641641 f"""
642642 SELECT repo,
643- COUNT(DISTINCT jira_key ) AS tickets,
643+ COUNT(DISTINCT external_key ) AS tickets,
644644 COUNT(*) AS cycles,
645645 ROUND(SUM(cost_usd)::numeric, 2) AS total_cost,
646646 ROUND(AVG(num_turns)::numeric, 1) AS avg_turns
@@ -656,7 +656,7 @@ async def api_analytics(request: Request) -> JSONResponse:
656656 ticket_rows = await pool .fetch (
657657 f"""
658658 SELECT
659- c.jira_key,
659+ c.external_key AS jira_key,
660660 t.title,
661661 t.status::text AS task_status,
662662 t.repo,
@@ -666,9 +666,9 @@ async def api_analytics(request: Request) -> JSONResponse:
666666 ROUND(SUM(c.cost_usd)::numeric, 2) AS total_cost,
667667 ROUND(EXTRACT(EPOCH FROM (MAX(c.timestamp) - MIN(c.timestamp)))/3600.0, 1) AS hours_span
668668 FROM cycles c
669- LEFT JOIN tasks t ON t.jira_key = c.jira_key
670- WHERE { date_filter } AND c.jira_key IS NOT NULL AND NOT c.no_work
671- GROUP BY c.jira_key , t.title, t.status, t.repo
669+ LEFT JOIN tasks t ON t.external_key = c.external_key
670+ WHERE { date_filter } AND c.external_key IS NOT NULL AND NOT c.no_work
671+ GROUP BY c.external_key , t.title, t.status, t.repo
672672 ORDER BY total_cycles DESC
673673 LIMIT 30
674674 """ ,
@@ -680,7 +680,7 @@ async def api_analytics(request: Request) -> JSONResponse:
680680 f"""
681681 SELECT
682682 COUNT(*) AS total_cycles,
683- COUNT(DISTINCT jira_key ) FILTER (WHERE jira_key IS NOT NULL AND NOT no_work) AS unique_tickets,
683+ COUNT(DISTINCT external_key ) FILTER (WHERE external_key IS NOT NULL AND NOT no_work) AS unique_tickets,
684684 ROUND(SUM(cost_usd)::numeric, 2) AS total_cost,
685685 ROUND(AVG(cost_usd) FILTER (WHERE NOT no_work)::numeric, 2) AS avg_cost_per_work_cycle,
686686 ROUND(AVG(num_turns) FILTER (WHERE NOT no_work)::numeric, 1) AS avg_turns,
@@ -714,10 +714,10 @@ async def api_analytics(request: Request) -> JSONResponse:
714714 COUNT(*) FILTER (WHERE review_count = 1) AS one_review,
715715 COUNT(*) FILTER (WHERE review_count > 1) AS multi_review
716716 FROM (
717- SELECT jira_key , COUNT(*) FILTER (WHERE work_type = 'pr_review') AS review_count
717+ SELECT external_key , COUNT(*) FILTER (WHERE work_type = 'pr_review') AS review_count
718718 FROM cycles
719- WHERE { date_filter } AND jira_key IS NOT NULL AND NOT no_work
720- GROUP BY jira_key
719+ WHERE { date_filter } AND external_key IS NOT NULL AND NOT no_work
720+ GROUP BY external_key
721721 ) sub
722722 """ ,
723723 * date_params ,
@@ -1044,9 +1044,21 @@ async def api_cycle_runs_by_task(request: Request) -> JSONResponse:
10441044
10451045
10461046def _task (row , slack_notif = None ) -> dict :
1047+ raw_artifacts = row .get ("artifacts" )
1048+ if isinstance (raw_artifacts , str ):
1049+ artifacts = json .loads (raw_artifacts )
1050+ elif raw_artifacts is not None :
1051+ artifacts = raw_artifacts
1052+ else :
1053+ artifacts = []
1054+
10471055 result = {
10481056 "id" : row ["id" ],
10491057 "jira_key" : row ["jira_key" ],
1058+ "external_key" : row .get ("external_key" ),
1059+ "source_type" : row .get ("source_type" ),
1060+ "source_url" : row .get ("source_url" ),
1061+ "artifacts" : artifacts ,
10501062 "status" : row ["status" ],
10511063 "repo" : row ["repo" ],
10521064 "branch" : row ["branch" ],
@@ -1084,6 +1096,8 @@ def _cycle(row) -> dict:
10841096 "is_error" : row ["is_error" ],
10851097 "no_work" : row ["no_work" ],
10861098 "jira_key" : row .get ("jira_key" ),
1099+ "external_key" : row .get ("external_key" ),
1100+ "source_type" : row .get ("source_type" ),
10871101 "repo" : row .get ("repo" ),
10881102 "work_type" : row .get ("work_type" ),
10891103 "summary" : row .get ("summary" ),
@@ -1096,6 +1110,8 @@ def _memory(row) -> dict:
10961110 "category" : row ["category" ],
10971111 "repo" : row ["repo" ],
10981112 "jira_key" : row ["jira_key" ],
1113+ "external_key" : row .get ("external_key" ),
1114+ "source_type" : row .get ("source_type" ),
10991115 "title" : row ["title" ],
11001116 "content" : row ["content" ],
11011117 "tags" : list (row ["tags" ]) if row ["tags" ] else [],
0 commit comments