
    `gj!                    L   d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZ ddlmZ  ed ed	d
            Z ej$                  e      ZdZdZe G d d             ZddZ ej2                  dej4                        ZddZddZddd	 	 	 	 	 	 	 ddZddddZy)u  Kanban triage specifier — flesh out a one-liner into a real spec.

Used by ``hermes kanban specify [task_id | --all]``. Takes a task that
lives in the Triage column (a rough idea, typically only a title), calls
the auxiliary LLM to produce:

  * A tightened title (optional — only replaces if the model proposes a
    materially different one)
  * A concrete body: goal, proposed approach, acceptance criteria

and then flips the task ``triage -> todo`` via
``kanban_db.specify_triage_task``. The dispatcher promotes it to
``ready`` on its next tick (or immediately if there are no open parents).

Design notes
------------

* This module intentionally mirrors ``hermes_cli/goals.py`` — same aux
  client pattern, same "empty config => skip, don't crash" tolerance.
  Keeps the surface area tiny and the failure modes predictable.

* The prompt is a short system + user pair. We ask for JSON with
  ``{title, body}``; if parsing fails, we fall back to treating the
  whole response as the body and leave the title untouched. No
  retry loop — one shot, keep cost bounded.

* Structured output / JSON mode is not requested explicitly so the
  specifier works on providers that don't implement it. The parse
  is lenient (tolerates markdown code fences around the JSON).
    )annotationsN)	dataclass)Optional)	kanban_db)env_inti   HERMES_KANBAN_SPECIFY_MAX_TOKENSip  u  You are the Kanban triage specifier for the Hermes Agent board.
A user dropped a rough idea into the Triage column. Your job is to turn it
into a concrete, actionable task spec that an autonomous worker can pick up
and execute without further clarification.

Output a single JSON object with exactly two keys:

  {
    "title": "<tightened task title, <= 80 chars, imperative voice>",
    "body":  "<multi-line spec, see structure below>"
  }

The body MUST include these sections, each prefixed with a bold markdown
heading, in this order:

  **Goal** — one sentence, user-facing outcome.
  **Approach** — 2-5 bullets on how a worker should tackle it.
  **Acceptance criteria** — checklist of concrete, verifiable conditions.
  **Out of scope** — short list of things NOT to touch (omit if nothing
      obvious; never invent scope creep).

Rules:
  - Keep the tightened title close in meaning to the original idea — do
    NOT invent a different project.
  - If the original idea is already detailed, preserve its substance and
    just reformat into the sections above.
  - Never add invented requirements the user didn't hint at.
  - No preamble, no closing remarks, no code fences around the JSON.
  - Output only the JSON object and nothing else.
z?Task id: {task_id}
Current title: {title}
Current body:
{body}
c                  B    e Zd ZU dZded<   ded<   dZded<   dZd	ed
<   y)SpecifyOutcomez*Result of specifying a single triage task.strtask_idboolok reasonNOptional[str]	new_title)__name__
__module____qualname____doc____annotations__r   r        L/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/kanban_specify.pyr
   r
   \   s#    4LHFC#I}#r   r
   c                8    t        |       |k  r| S | d |dz
   dz   S )N   u   …)len)textlimits     r   	_truncater    f   s)    
4yE%!)u$$r   z^\s*```(?:json)?\s*|\s*```\s*$c                T   | syt         j                  d| j                               }|j                  d      }|j	                  d      }|dk(  s
|dk(  s||k  ry|||dz    }	 t        j                  |      }t        |t              sy|S # t        t
        j                  f$ r Y yw xY w)u}   Lenient JSON extraction — tolerates fenced code blocks and
    leading/trailing whitespace. Returns None if nothing parses.Nr   {}r   )	_FENCE_REsubstripfindrfindjsonloads
ValueErrorJSONDecodeError
isinstancedict)rawstrippedfirstlast	candidatevals         r   _extract_json_blobr6   o   s     }}R-HMM#E>>#D{dbjDEM*Ijj# c4 J	 ,,- s   #B B'&B'c                     t         j                  j                  d      xs# t         j                  j                  d      xs dS )z~Mirror of ``hermes_cli.kanban._profile_author``. Kept local to
    avoid a circular import when kanban.py imports this module.HERMES_PROFILEUSER	specifier)osenvirongetr   r   r   _profile_authorr>      s7     	

'( 	::>>&!	r   )authortimeoutc          	        t        j                         5 }t        j                  ||       }ddd       t        | dd      S |j                  dk7  rt        | dd|j                  d      S 	 ddlm} t        j                  |j                  t        |j                  xs dd      t        |j                  xs dd            }	  |ddt         dd|dgdt"        |xs d      }	 |j*                  d   j,                  j.                  xs dj1                         }	t3        |	      }
|
$|	j1                         }|st        | dd      S d}|}n|
j5                  d      }|
j5                  d      }t7        |t8              r |j1                         r|j1                         nd}t7        |t8              r|j1                         r|nd}||t        | dd      S t        j                         5 }t        j:                  || |||xs
 t=                     }ddd       st        | dd      S t        | dd |!      S # 1 sw Y   xY w# t        $ r-}t        j                  d	|       t        | dd
      cY d}~S d}~ww xY w# t        $ rD}t        j%                  d| |       t        | ddt'        |      j(                         cY d}~S d}~ww xY w# t        $ r d}	Y w xY w# 1 sw Y   xY w)"uR  Specify a single triage task and promote it to ``todo``.

    Returns an outcome describing what happened. Never raises for expected
    failure modes (task not in triage, no aux client configured, API
    error, malformed response) — those surface via ``ok=False`` so the
    ``--all`` sweep can continue past individual failures.
    NFzunknown task idtriageztask is not in triage (status=)r   )call_llmz+specify: auxiliary client import failed: %szauxiliary client unavailabler   i  z	(no body)i  )r   titlebodytriage_specifiersystem)rolecontentuserg333333?x   )taskmessagestemperature
max_tokensr@   u1   specify: API call failed for %s (%s) — skippingzLLM error: zLLM returned an empty responserE   rF   z#LLM response missing title and body)rE   rF   r?   z)task moved out of triage before promotionT	specified)r   )kbconnect_closingget_taskr
   statusagent.auxiliary_clientrD   	Exceptionloggerdebug_USER_TEMPLATEformatidr    rE   rF   _SYSTEM_PROMPTr   infotyper   choicesmessagerJ   r'   r6   r=   r.   r   specify_triage_taskr>   )r   r?   r@   connrM   rD   excuser_msgrespr0   parsedstripped_rawr   new_body	title_valbody_valr   s                    r   specify_taskrl      s    
			 *{{4)*|gu.?@@{{hU<T[[O1M
 	
N3
 $$

(b#.tyy/K6 % H
 #!n=H5 7Ns	
&||A&&..4";;=  $F ~ yy{! @  	JJw'	::f% )S)ioo.? OO 	 #8S1hnn6FHD 	 	 1! E  
			 
##._.

  UG
 	
 '4	JJ}* *  NBCHgu.LMMN0  
?S	
 Uk$s)*<*<)=>
 	

  D
 
se   H9.I !I? )5K ,'K!9I	I<"I71I<7I<?	K9KKKKK!K*)tenantc                    t        j                         5 }t        j                  |d| d      }ddd       D cg c]  }|j                   c}S # 1 sw Y   #xY wc c}w )z{Return task ids currently in the triage column.

    ``tenant`` narrows the sweep; ``None`` returns every triage task.
    rB   F)rU   rm   include_archivedN)rR   rS   
list_tasksr\   )rm   rc   tasksts       r   list_triage_idsrs      s\    
 
			 
"	

   QADD  
 
 !s   AAA)r   r   r   intreturnr   )r0   r   ru   zOptional[dict])ru   r   )r   r   r?   r   r@   zOptional[int]ru   r
   )rm   r   ru   z	list[str]) r   
__future__r   r*   loggingr;   redataclassesr   typingr   
hermes_clir   rR   utilsr   maxr   	getLoggerr   rX   r]   rZ   r
   r    compile
IGNORECASEr%   r6   r>   rl   rs   r   r   r   <module>r      s   > #   	 	 !  & #&.5$  
 
		8	$@ $ $ $% BJJ8"--H	* !!	kKkK kK 	kK
 kK\ 04 !r   