analyzthis_design 2.0.0 → 2.1.0

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 (69) hide show
  1. package/HOW-TO-USE.md +436 -0
  2. package/README.md +29 -13
  3. package/agents/cards/evolve-check.md +38 -0
  4. package/agents/manifests/evolve-check.json +16 -0
  5. package/dist/HOW-TO-USE.md +15 -3
  6. package/dist/README.md +29 -13
  7. package/dist/agents/cards/evolve-check.md +38 -0
  8. package/dist/agents/manifests/evolve-check.json +16 -0
  9. package/dist/bin/cli.js +1225 -1
  10. package/dist/lib/cache.js +111 -1
  11. package/dist/lib/chunk-executor.js +219 -1
  12. package/dist/lib/chunk-models.js +228 -1
  13. package/dist/lib/chunk-planner.js +328 -1
  14. package/dist/lib/chunk-router.js +66 -1
  15. package/dist/lib/chunk-run.js +199 -1
  16. package/dist/lib/chunk-synthesis.js +176 -1
  17. package/dist/lib/chunk-telemetry.js +88 -1
  18. package/dist/lib/collect.js +858 -1
  19. package/dist/lib/cost.js +119 -1
  20. package/dist/lib/dedup.js +167 -1
  21. package/dist/lib/deliberation.js +721 -1
  22. package/dist/lib/design-spec.js +236 -1
  23. package/dist/lib/evolution-metrics.js +197 -0
  24. package/dist/lib/evolve.js +361 -1
  25. package/dist/lib/export.js +77 -1
  26. package/dist/lib/feedback-submit.js +324 -1
  27. package/dist/lib/feedback.js +182 -1
  28. package/dist/lib/host-llm.js +251 -1
  29. package/dist/lib/install.js +301 -1
  30. package/dist/lib/knowledge.js +384 -1
  31. package/dist/lib/lessons.js +217 -1
  32. package/dist/lib/moodboard.js +563 -1
  33. package/dist/lib/orchestrator/run.js +935 -1
  34. package/dist/lib/outcome.js +193 -1
  35. package/dist/lib/platforms.js +166 -1
  36. package/dist/lib/provider.js +57 -1
  37. package/dist/lib/query-expander.js +83 -1
  38. package/dist/lib/ranker.js +105 -1
  39. package/dist/lib/reference-pack.js +221 -0
  40. package/dist/lib/research.js +143 -1
  41. package/dist/lib/retrieve.js +131 -1
  42. package/dist/lib/session.js +185 -1
  43. package/dist/lib/source-discovery.js +486 -1
  44. package/dist/lib/synthesis.js +155 -1
  45. package/dist/lib/token-gate.js +46 -1
  46. package/dist/skills/design-reference/google-fonts.csv +1924 -1924
  47. package/dist/skills/design-reference/products.csv +162 -162
  48. package/dist/skills/design-reference/schema.json +159 -0
  49. package/dist/skills/design-reference/stacks/angular.csv +1 -1
  50. package/dist/skills/design-reference/stacks/astro.csv +1 -1
  51. package/dist/skills/design-reference/stacks/laravel.csv +2 -2
  52. package/dist/skills/design-reference/stacks/threejs.csv +54 -54
  53. package/dist/skills/design-reference/styles.csv +85 -85
  54. package/dist/skills/design-reference/typography.csv +75 -74
  55. package/dist/skills/design-reference/ui-reasoning.csv +1 -1
  56. package/dist/skills/evolve-check/SKILL.md +106 -0
  57. package/package.json +8 -8
  58. package/scripts/validate-csvs.js +197 -0
  59. package/skills/design-reference/google-fonts.csv +1924 -1924
  60. package/skills/design-reference/products.csv +162 -162
  61. package/skills/design-reference/schema.json +159 -0
  62. package/skills/design-reference/stacks/angular.csv +1 -1
  63. package/skills/design-reference/stacks/astro.csv +1 -1
  64. package/skills/design-reference/stacks/laravel.csv +2 -2
  65. package/skills/design-reference/stacks/threejs.csv +54 -54
  66. package/skills/design-reference/styles.csv +85 -85
  67. package/skills/design-reference/typography.csv +75 -74
  68. package/skills/design-reference/ui-reasoning.csv +1 -1
  69. package/skills/evolve-check/SKILL.md +106 -0
@@ -1 +1,721 @@
1
- 'use strict';const fs=require('fs'),path=require('path'),os=require('os'),dedup=require('./dedup'),CONFIG_FILE=path['join'](os['homedir'](),'.analyzthis_design','config.json'),DEFAULTS={'satisfaction_threshold':0.4,'max_rounds':0x3,'parallel_pairs':[['noor','anuj'],['meera','priya']],'objection_token_cap':0x258,'prior_output_chars_first':0x320,'prior_output_chars_rebuttal':0x190,'prior_output_chars_produce':0x4b0,'context_pack_chars_objection':0x5dc,'context_pack_chars_produce':0xbb8,'escalate_to_raj_after_round':0x2,'default_mode':'adversarial'},DELIBERATION_JSON_HINT='\x0aEnd\x20your\x20response\x20with\x20a\x20fenced\x20JSON\x20block:\x0a```json\x20deliberation\x0a{\x0a\x20\x20\x22grounding\x22:\x20[{\x22type\x22:\x20\x22task_map\x22,\x20\x22ref\x22:\x20\x22task_map[0]\x22,\x20\x22note\x22:\x20\x22...\x22}],\x0a\x20\x20\x22satisfaction_with_prior\x22:\x200.3,\x0a\x20\x20\x22accepts_prior\x22:\x20false,\x0a\x20\x20\x22objections\x22:\x20[{\x22target_persona\x22:\x20\x22arjun\x22,\x20\x22claim\x22:\x20\x22...\x22,\x20\x22evidence_required\x22:\x20\x22...\x22,\x20\x22blocking\x22:\x20true,\x20\x22grounded_in\x22:\x20\x22...\x22}],\x0a\x20\x20\x22questions\x22:\x20[\x22...\x22],\x0a\x20\x20\x22revisions\x22:\x20[\x22...\x22],\x0a\x20\x20\x22verdict\x22:\x20\x22CONTEST\x22\x0a}\x0a```';function loadDeliberationConfig(a){let b=a;if(!b)try{b=fs['existsSync'](CONFIG_FILE)?JSON['parse'](fs['readFileSync'](CONFIG_FILE,'utf8')):{};}catch{b={};}const c=b['deliberation']||{};return{'satisfaction_threshold':c['satisfaction_threshold']??DEFAULTS['satisfaction_threshold'],'max_rounds':c['max_rounds']??DEFAULTS['max_rounds'],'parallel_pairs':c['parallel_pairs']??DEFAULTS['parallel_pairs'],'objection_token_cap':c['objection_token_cap']??DEFAULTS['objection_token_cap'],'prior_output_chars_first':c['prior_output_chars_first']??DEFAULTS['prior_output_chars_first'],'prior_output_chars_rebuttal':c['prior_output_chars_rebuttal']??DEFAULTS['prior_output_chars_rebuttal'],'prior_output_chars_produce':c['prior_output_chars_produce']??DEFAULTS['prior_output_chars_produce'],'context_pack_chars_objection':c['context_pack_chars_objection']??DEFAULTS['context_pack_chars_objection'],'context_pack_chars_produce':c['context_pack_chars_produce']??DEFAULTS['context_pack_chars_produce'],'escalate_to_raj_after_round':c['escalate_to_raj_after_round']??DEFAULTS['escalate_to_raj_after_round'],'default_mode':c['default_mode']??DEFAULTS['default_mode']};}function readVaultExcerpt(a,b=0x7d0){if(!a||!fs['existsSync'](a))return'';const c=[],d=['PRDs','Brand','Components','Product'];for(const e of d){const g=path['join'](a,e);if(!fs['existsSync'](g))continue;try{for(const h of fs['readdirSync'](g)['filter'](i=>i['endsWith']('.md'))['slice'](0x0,0x2)){const i=fs['readFileSync'](path['join'](g,h),'utf8');c['push']('---\x20'+e+'/'+h+'\x20---\x0a'+i['slice'](0x0,0x1f4));}}catch{}if(c['join']('')['length']>b)break;}return c['join']('\x0a\x0a')['slice'](0x0,b);}function buildContextPack(a,b,c=''){const d=(a['task_map']||[])['slice'](0x0,0x8)['map']((e,f)=>({'id':f,'task':e['task'],'frequency':e['frequency'],'priority':e['priority']}));return{'task':b,'figma':c||a['figma_node']?.['url']||'','task_map':d,'task_map_summary':a['digest']?.['task_map_summary']||b['slice'](0x0,0xf0),'hierarchy_top3':a['digest']?.['hierarchy_top3']||a['information_hierarchy']?.['ranking']?.['slice'](0x0,0x3)||[],'ds_checklist':a['ds_checklist']||{},'ds_at_risk':a['digest']?.['ds_at_risk']||[],'design_spec_status':a['digest']?.['design_spec_status']||a['design_spec']?.['status']||'','vault_excerpt':readVaultExcerpt(a['vault_path']),'mode':a['mode']||'assess_only'};}function parseDeliberationOutput(a){const b={'grounding':[],'satisfaction_with_prior':0x0,'accepts_prior':![],'objections':[],'questions':[],'revisions':[],'verdict':'CONTEST','synthesis':''};if(!a||typeof a!=='string')return{...b,'parse_error':'empty'};const c=a['match'](/```json\s*deliberation\s*([\s\S]*?)```/i)||a['match'](/```json\s*([\s\S]*?)```/);if(c)try{const g=JSON['parse'](c[0x1]['trim']());return{'grounding':g['grounding']||[],'satisfaction_with_prior':Number(g['satisfaction_with_prior'])||0x0,'accepts_prior':!!g['accepts_prior'],'objections':Array['isArray'](g['objections'])?g['objections']:[],'questions':Array['isArray'](g['questions'])?g['questions']:[],'revisions':Array['isArray'](g['revisions'])?g['revisions']:[],'verdict':g['verdict']||'CONTEST','synthesis':g['synthesis']||''};}catch(h){return{...b,'parse_error':h['message']};}const d=a['toLowerCase'](),f=/objection|contest|disagree|not satisfied|blocking|evidence required/['test'](d);return{...b,'satisfaction_with_prior':f?0.2:0.5,'accepts_prior':!f,'verdict':f?'CONTEST':'ACCEPT','synthesis':a['slice'](0x0,0x1f4),'parse_error':'no_json_fence'};}function objectionKey(a){return((a['target_persona']||'')+':'+(a['claim']||'')['slice'](0x0,0x50))['toLowerCase']();}function computeRoundStatus(a,b,c=[]){const d=[...c],e=new Set();let f=0x0,g=0x0;const h=new Set();for(const n of a){const p=n['parsed']||{};if(p['objections']?.['length'])for(const q of p['objections']){f+=0x1;const r=objectionKey(q);if(q['blocking']&&!p['accepts_prior']){!e['has'](r)&&(e['add'](r),d['push']({...q,'raised_by':n['persona'],'round':n['round']}));if(q['target_persona'])h['add'](q['target_persona']);}}if(p['accepts_prior']&&p['satisfaction_with_prior']>=b)g+=p['objections']?.['filter'](s=>!s['blocking'])['length']||0x0;else n['persona']&&h['add'](n['persona']);}const i=d['filter'](s=>{const t=a['some'](u=>u['persona']===s['target_persona']&&u['parsed']?.['accepts_prior']&&(u['parsed']?.['satisfaction_with_prior']||0x0)>=b);if(t)g+=0x1;return!t;}),j=i['filter'](s=>s['blocking'])['length'],l=new Set(),m={};for(const s of i){const t=objectionKey(s);m[t]=(m[t]||0x0)+0x1;if(m[t]>=0x2)l['add'](t);}return{'open_objections':i,'blocking_count':j,'repeat_claims':[...l],'personas_must_rerun':[...h],'consensus_reached':j===0x0&&a['length']>0x0&&a['every'](u=>(u['parsed']?.['satisfaction_with_prior']||0x0)>=b||u['parsed']?.['accepts_prior']),'objections_raised':f,'objections_resolved':g};}function shouldEscalateToRaj(a,b,c){if(a['blocking_count']>=0x2)return!![];if(a['repeat_claims']['length']>=0x1)return!![];if(c>=(b['escalate_to_raj_after_round']||0x2)&&a['blocking_count']>=0x1)return!![];return![];}function getDeliberationGroups(a,{full_chain:b,ideation_chain:c,experts:d,mode:e}){const f=a['deliberation_groups']||{};if(c)return f['ideation']||[['meera'],['noor','anuj'],['arjun'],['zara','priya']];if(b)return f['critique']||[['arjun'],['meera','priya'],['zara']];if(e==='lite'){const h=f['lite']||[['arjun'],['meera']],i=new Set(d||[]);return h['map'](j=>j['filter'](k=>i['has'](k)))['filter'](j=>j['length']);}const g=[];for(const j of d||[])g['push']([j]);return g;}function summarizePriorOutputs(a,b,{round:round=0x0,isProduceRound:isProduceRound=![],limits:limits={}}={}){const c=isProduceRound?limits['produce']??0x4b0:round>0x0?limits['rebuttal']??0x190:limits['first']??0x320,e=[];for(const f of b){const g=a[f];if(!g?.['text'])continue;if(round>0x0&&!isProduceRound&&g['deliberation']){const h=g['deliberation'],i={'verdict':h['verdict'],'satisfaction':h['satisfaction_with_prior'],'objections':(h['objections']||[])['slice'](0x0,0x3)['map'](k=>({'target':k['target_persona'],'claim':(k['claim']||'')['slice'](0x0,0x78),'grounded_in':k['grounded_in']})),'revisions':(h['revisions']||[])['slice'](0x0,0x2)},j=g['text']['replace'](/```json\s*deliberation[\s\S]*?```/i,'')['trim']()['slice'](0x0,0xb4);e['push']('###\x20'+f+'\x0a'+JSON['stringify'](i)+'\x0a'+j+(g['text']['length']>0xb4?'…':''));continue;}e['push']('###\x20'+f+'\x0a'+g['text']['slice'](0x0,c)+(g['text']['length']>c?'…':''));}return e['join']('\x0a\x0a');}function buildObjectionPrompt({persona:a,contextPack:b,priorOutputs:c,openObjections:d,round:e,maxRounds:f,threshold:g,isProduceRound:h,task:i,routingLine:j,referenceCitations:k,coveredPointsSummary:l,limits:limits={}}){const m=Object['keys'](c||{})['filter'](r=>r!==a),n=summarizePriorOutputs(c,m,{'round':e,'isProduceRound':h,'limits':{'first':limits['prior_output_chars_first'],'rebuttal':limits['prior_output_chars_rebuttal'],'produce':limits['prior_output_chars_produce']}}),o=h?limits['context_pack_chars_produce']??0xbb8:limits['context_pack_chars_objection']??0x5dc,p=h?'PRODUCE\x20MODE\x20(final\x20round):\x20synthesize\x20your\x20full\x20output\x20after\x20deliberation.':'REVIEW\x20MODE\x20(round\x20'+e+'/'+f+'):\x20critique\x20prior\x20output\x20—\x20do\x20NOT\x20rewrite\x20full\x20wireframe/critique\x20essays.',q=[p,'Satisfaction\x20threshold:\x20'+g+'\x20—\x20default\x20accepts_prior=false\x20until\x20evidence\x20addresses\x20objections.','','Task:\x20'+i,j||'',k?'Reference\x20data:\x0a'+k:'','','Context\x20pack\x20(cite\x20these\x20in\x20grounding[]):',JSON['stringify'](b,null,0x2)['slice'](0x0,o)];return n&&q['push']('','Prior\x20persona\x20outputs\x20(read\x20critically\x20—\x20contest\x20specific\x20claims):',n),d?.['length']&&q['push']('','Open\x20objections\x20you\x20must\x20address\x20or\x20extend:',JSON['stringify'](d['slice'](0x0,0xa),null,0x2)),l&&q['push']('',l),!h?(e>0x0&&q['push']('','Rebuttal\x20round\x20'+e+'/'+f+':\x20address\x20open\x20objections\x20with\x20NEW\x20evidence.\x20Do\x20not\x20repeat\x20prior\x20paragraphs\x20verbatim.'),q['push']('','Requirements:','-\x20Every\x20objection\x20must\x20include\x20grounded_in\x20citing\x20task_map,\x20PRD,\x20UI\x20region,\x20or\x20DS\x20token.','-\x20Ask\x20at\x20least\x20one\x20contextual\x20question\x20about\x20the\x20real\x20task/UI/PRD.','-\x20Do\x20not\x20write\x20generic\x20handoff\x20paragraphs.','-\x20Do\x20NOT\x20repeat\x20points\x20already\x20covered\x20by\x20prior\x20personas.\x20If\x20you\x20agree,\x20state\x20\x22agree\x20with\x20<persona>\x20on\x20<point>\x22\x20and\x20move\x20on.','-\x20HARD\x20OUTPUT\x20CAP:\x20~'+(limits['objection_token_cap']??0x258)+'\x20tokens\x20—\x20lite\x20schema\x20+\x20deliberation\x20JSON\x20only.',DELIBERATION_JSON_HINT)):q['push']('','Produce\x20final\x20structured\x20output\x20+\x20deliberation\x20JSON\x20block.',DELIBERATION_JSON_HINT),q['filter'](Boolean)['join']('\x0a');}function buildRajPrompt(a,b,c,d){return['STALEMATE\x20—\x20arbitrate\x20unresolved\x20persona\x20disagreements\x20using\x20your\x205\x20product\x20principles.','','Context:',JSON['stringify'](a,null,0x2)['slice'](0x0,0x7d0),'','Persona\x20outputs:',summarizePriorOutputs(b,Object['keys'](b||{})),'','Open\x20objections:',JSON['stringify'](c,null,0x2),'','Round\x20log\x20summary:',JSON['stringify'](d['slice'](-0x8)['map'](e=>({'round':e['round'],'persona':e['persona'],'verdict':e['parsed']?.['verdict'],'blocking':e['parsed']?.['objections']?.['filter'](f=>f['blocking'])['length']})),null,0x2),'','Output\x20mandatory\x20Stalemate\x20Resolution\x20block\x20+\x20deliberation\x20JSON\x20with\x20verdict\x20SHIP|REVISE|BLOCK.',DELIBERATION_JSON_HINT]['join']('\x0a');}function extractScore(a){const b=a['match'](/(\d+(?:\.\d+)?)\s*\/\s*5/)||a['match'](/score[:\s]+(\d+(?:\.\d+)?)/i);return b?Number(b[0x1]):null;}async function runDeliberation(a){const {chain:b,experts:c,full_chain:d,ideation_chain:e,mode:f,task:g,routing:h,state:i,figma:j,config:k,callPersona:l,loadPersonaContext:m,buildReferencePack:n,noDeliberate:noDeliberate=![]}=a,o=loadDeliberationConfig(k),p=a['satisfaction_threshold']??o['satisfaction_threshold'],q=a['max_rounds']??o['max_rounds'];if(noDeliberate||o['default_mode']==='legacy')return runLegacySequential(a);const r=buildContextPack(i,g,j),s=getDeliberationGroups(b,{'full_chain':d,'ideation_chain':e,'experts':c,'mode':f}),t=a['resume_checkpoint']||i['host_run']?.['checkpoint']||null,u={...t?.['persona_outputs']||i['persona_outputs']||{}},v=[...t?.['round_log']||[]];let w=[...t?.['open_objections']||[]],x=t?.['round']??0x0,y=t?.['raj_escalated']??![],z=t?.['llm_calls']??0x0,A=t?.['input_tokens_est']??0x0,B=t?.['output_tokens_est']??0x0;const C=[...t?.['effort_log']||[]];let D=t?.['objections_raised']??0x0,E=t?.['objections_resolved']??0x0,F=[...t?.['covered_points']||i['covered_points']||[]];const G='Routing:\x20'+h['problem_type']+'\x20→\x20'+c['join'](',\x20'),H=t?.['group_index']??0x0,I=t?.['group_round']??0x0;for(let M=H;M<s['length'];M++){const N=s[M];let O=M===H?I:0x0;while(O<=q){const P=O===q,Q=N['filter'](T=>{if(O===0x0)return!![];const U=computeRoundStatus(v,p,w);return U['personas_must_rerun']['includes'](T)||!v['some'](V=>V['persona']===T&&V['parsed']?.['accepts_prior']);});if(!Q['length']&&O>0x0)break;const R=async T=>{const {system:U,manifest:V,effort:W,callProvider:X,callModel:Y,maxTokens:Z}=await m(T,{'isProduceRound':P,'isObjectionRound':!P,'objectionCap':o['objection_token_cap']}),a0=n?await n(T,g):null,a1=F['length']?dedup['buildCoveredPointsSummaryFromPoints'](F):'',a2=buildObjectionPrompt({'persona':T,'contextPack':r,'priorOutputs':u,'openObjections':w,'round':O,'maxRounds':q,'threshold':p,'isProduceRound':P,'task':g,'routingLine':G,'referenceCitations':a0?.['citations']?.['join']('\x0a'),'coveredPointsSummary':a1,'limits':o});let a3;try{a3=await l(T,U,a2,{'provider':X,'model':Y,'maxTokens':P?Z:Math['min'](Z,o['objection_token_cap']),'effort':W});}catch(a6){a6['name']==='HostLlmPendingError'&&(a6['checkpoint']={'group_index':M,'group_round':O,'persona_outputs':u,'round_log':v,'round':x,'open_objections':w,'raj_escalated':y,'llm_calls':z,'input_tokens_est':A,'output_tokens_est':B,'effort_log':C,'objections_raised':D,'objections_resolved':E,'covered_points':F});throw a6;}z+=0x1,A+=a3['input_tokens_est']||0x0,B+=a3['output_tokens_est']||0x0,C['push'](a3['effort_log_entry']);const a4=parseDeliberationOutput(a3['text']),a5={'round':x,'persona':T,'parsed':a4,'raw_length':a3['text']['length'],'at':new Date()['toISOString']()};v['push'](a5),u[T]={'text':a3['text'],'deliberation':a4,'at':a5['at'],'accepted':null,'full_prompt':a3['full_prompt']||null,'structured_output':a3['structured_output']||null};try{const a7=dedup['extractCoveredPoints'](a3['text'],T);a7&&a7['length']&&F['push']['apply'](F,a7);}catch{}return a5;};await Promise['all'](Q['map'](T=>R(T))),x+=0x1;const S=computeRoundStatus(v,p,w);w=S['open_objections'],D+=S['objections_raised'],E+=S['objections_resolved'];if(S['consensus_reached'])break;O+=0x1;if(O>q)break;}}const J=computeRoundStatus(v,p,w);w=J['open_objections'];if(shouldEscalateToRaj(J,o,x)&&!y){y=!![];const T=await m('raj',{'isProduceRound':!![],'isObjectionRound':![]}),U=buildRajPrompt(r,u,w,v),V=await l('raj',T['system'],U,{'provider':T['callProvider'],'model':T['callModel'],'maxTokens':T['maxTokens'],'effort':'hard'});z+=0x1,A+=V['input_tokens_est']||0x0,B+=V['output_tokens_est']||0x0;const W=parseDeliberationOutput(V['text']);v['push']({'round':x,'persona':'raj','parsed':W,'raw_length':V['text']['length'],'at':new Date()['toISOString']()}),u['raj']={'text':V['text'],'deliberation':W,'at':new Date()['toISOString'](),'accepted':null},w=[];}const K={...i['digest']?.['prior_scores']||{}};for(const [X,Y]of Object['entries'](u)){const Z=extractScore(Y['text']);if(Z!=null)K[X]=Z;}const L=computeRoundStatus(v,p,[]);return{'persona_outputs':u,'covered_points':F,'deliberation':{'round':x,'max_rounds':q,'satisfaction_threshold':p,'open_objections':L['open_objections'],'round_log':v,'consensus_reached':L['consensus_reached'],'raj_escalated':y,'mode':'adversarial'},'digest_patch':{'prior_scores':K},'metrics':{'llm_calls':z,'input_tokens_est':A,'output_tokens_est':B,'deliberation_rounds':x,'objections_raised':D,'objections_resolved':E,'raj_escalations':y?0x1:0x0,'effort_log':C}};}async function runLegacySequential(a){const {experts:b,task:c,routing:d,state:e,figma:f,callPersona:g,loadPersonaContext:h,buildReferencePack:i}=a,j={};let k=0x0,l=0x0,m=0x0;const n=[],o=buildContextPack(e,c,f),p={...e['digest']||{}};for(const q of b){const r=await h(q,{'isProduceRound':!![],'isObjectionRound':![]}),s=i?await i(q,c):null,t=summarizePriorOutputs(j,Object['keys'](j)),u=['Task:\x20'+c,f?'Figma:\x20'+f:'','Routing:\x20'+d['problem_type']+'\x20→\x20'+b['join'](',\x20'),s?'Reference\x20data:\x0a'+s['citations']['join']('\x0a'):'','Session\x20digest:\x20'+JSON['stringify'](p)['slice'](0x0,0x5dc),'Context\x20pack:\x20'+JSON['stringify'](o)['slice'](0x0,0x5dc),t?'Prior\x20persona\x20outputs:\x0a'+t:'','Produce\x20your\x20structured\x20output\x20block\x20as\x20defined\x20in\x20your\x20card/skill.']['filter'](Boolean)['join']('\x0a'),v=await g(q,r['system'],u,{'provider':r['callProvider'],'model':r['callModel'],'maxTokens':r['maxTokens'],'effort':r['effort']});k+=0x1,l+=v['input_tokens_est']||0x0,m+=v['output_tokens_est']||0x0,n['push'](v['effort_log_entry']),j[q]={'text':v['text'],'at':new Date()['toISOString'](),'accepted':null,'full_prompt':v['full_prompt']||null,'structured_output':v['structured_output']||null};const w=extractScore(v['text']);w!=null&&(p['prior_scores']={...p['prior_scores']||{},[q]:w});}return{'persona_outputs':j,'covered_points':[],'deliberation':{'round':0x0,'max_rounds':0x0,'satisfaction_threshold':0x0,'open_objections':[],'round_log':[],'consensus_reached':!![],'raj_escalated':![],'mode':'legacy'},'digest_patch':{'prior_scores':p['prior_scores']||{}},'metrics':{'llm_calls':k,'input_tokens_est':l,'output_tokens_est':m,'deliberation_rounds':0x0,'objections_raised':0x0,'objections_resolved':0x0,'raj_escalations':0x0,'effort_log':n}};}module['exports']={'loadDeliberationConfig':loadDeliberationConfig,'buildContextPack':buildContextPack,'parseDeliberationOutput':parseDeliberationOutput,'computeRoundStatus':computeRoundStatus,'shouldEscalateToRaj':shouldEscalateToRaj,'getDeliberationGroups':getDeliberationGroups,'buildObjectionPrompt':buildObjectionPrompt,'buildRajPrompt':buildRajPrompt,'runDeliberation':runDeliberation,'runLegacySequential':runLegacySequential,'DELIBERATION_JSON_HINT':DELIBERATION_JSON_HINT,'DEFAULTS':DEFAULTS};
1
+ 'use strict';
2
+
3
+ /**
4
+ * Adversarial deliberation engine (v1.19).
5
+ * Personas review prior output with low default satisfaction until consensus or Raj.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const dedup = require('./dedup');
12
+
13
+ const CONFIG_FILE = path.join(os.homedir(), '.analyzthis_design', 'config.json');
14
+
15
+ const DEFAULTS = {
16
+ satisfaction_threshold: 0.4,
17
+ max_rounds: 3,
18
+ parallel_pairs: [['noor', 'anuj'], ['meera', 'priya']],
19
+ objection_token_cap: 600,
20
+ prior_output_chars_first: 800,
21
+ prior_output_chars_rebuttal: 400,
22
+ prior_output_chars_produce: 1200,
23
+ context_pack_chars_objection: 1500,
24
+ context_pack_chars_produce: 3000,
25
+ escalate_to_raj_after_round: 2,
26
+ default_mode: 'adversarial',
27
+ };
28
+
29
+ const DELIBERATION_JSON_HINT = `
30
+ End your response with a fenced JSON block:
31
+ \`\`\`json deliberation
32
+ {
33
+ "grounding": [{"type": "task_map", "ref": "task_map[0]", "note": "..."}],
34
+ "satisfaction_with_prior": 0.3,
35
+ "accepts_prior": false,
36
+ "objections": [{"target_persona": "arjun", "claim": "...", "evidence_required": "...", "blocking": true, "grounded_in": "..."}],
37
+ "questions": ["..."],
38
+ "revisions": ["..."],
39
+ "verdict": "CONTEST"
40
+ }
41
+ \`\`\``;
42
+
43
+ function loadDeliberationConfig(configIn) {
44
+ let cfg = configIn;
45
+ if (!cfg) {
46
+ try {
47
+ cfg = fs.existsSync(CONFIG_FILE) ? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) : {};
48
+ } catch {
49
+ cfg = {};
50
+ }
51
+ }
52
+ const d = cfg.deliberation || {};
53
+ return {
54
+ satisfaction_threshold: d.satisfaction_threshold ?? DEFAULTS.satisfaction_threshold,
55
+ max_rounds: d.max_rounds ?? DEFAULTS.max_rounds,
56
+ parallel_pairs: d.parallel_pairs ?? DEFAULTS.parallel_pairs,
57
+ objection_token_cap: d.objection_token_cap ?? DEFAULTS.objection_token_cap,
58
+ prior_output_chars_first: d.prior_output_chars_first ?? DEFAULTS.prior_output_chars_first,
59
+ prior_output_chars_rebuttal: d.prior_output_chars_rebuttal ?? DEFAULTS.prior_output_chars_rebuttal,
60
+ prior_output_chars_produce: d.prior_output_chars_produce ?? DEFAULTS.prior_output_chars_produce,
61
+ context_pack_chars_objection: d.context_pack_chars_objection ?? DEFAULTS.context_pack_chars_objection,
62
+ context_pack_chars_produce: d.context_pack_chars_produce ?? DEFAULTS.context_pack_chars_produce,
63
+ escalate_to_raj_after_round: d.escalate_to_raj_after_round ?? DEFAULTS.escalate_to_raj_after_round,
64
+ default_mode: d.default_mode ?? DEFAULTS.default_mode,
65
+ };
66
+ }
67
+
68
+ function readVaultExcerpt(vaultPath, maxChars = 2000) {
69
+ if (!vaultPath || !fs.existsSync(vaultPath)) return '';
70
+ const parts = [];
71
+ const folders = ['PRDs', 'Brand', 'Components', 'Product'];
72
+ for (const folder of folders) {
73
+ const dir = path.join(vaultPath, folder);
74
+ if (!fs.existsSync(dir)) continue;
75
+ try {
76
+ for (const f of fs.readdirSync(dir).filter((n) => n.endsWith('.md')).slice(0, 2)) {
77
+ const text = fs.readFileSync(path.join(dir, f), 'utf8');
78
+ parts.push(`--- ${folder}/${f} ---\n${text.slice(0, 500)}`);
79
+ }
80
+ } catch { /* skip unreadable */ }
81
+ if (parts.join('').length > maxChars) break;
82
+ }
83
+ return parts.join('\n\n').slice(0, maxChars);
84
+ }
85
+
86
+ /**
87
+ * Build shared context pack from session state.
88
+ */
89
+ function buildContextPack(state, task, figma = '') {
90
+ const taskMap = (state.task_map || []).slice(0, 8).map((t, i) => ({
91
+ id: i,
92
+ task: t.task,
93
+ frequency: t.frequency,
94
+ priority: t.priority,
95
+ }));
96
+
97
+ return {
98
+ task,
99
+ figma: figma || state.figma_node?.url || '',
100
+ task_map: taskMap,
101
+ task_map_summary: state.digest?.task_map_summary || task.slice(0, 240),
102
+ hierarchy_top3: state.digest?.hierarchy_top3 || state.information_hierarchy?.ranking?.slice(0, 3) || [],
103
+ ds_checklist: state.ds_checklist || {},
104
+ ds_at_risk: state.digest?.ds_at_risk || [],
105
+ design_spec_status: state.digest?.design_spec_status || state.design_spec?.status || '',
106
+ vault_excerpt: readVaultExcerpt(state.vault_path),
107
+ mode: state.mode || 'assess_only',
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Parse deliberation JSON from persona LLM output.
113
+ */
114
+ function parseDeliberationOutput(text) {
115
+ const empty = {
116
+ grounding: [],
117
+ satisfaction_with_prior: 0,
118
+ accepts_prior: false,
119
+ objections: [],
120
+ questions: [],
121
+ revisions: [],
122
+ verdict: 'CONTEST',
123
+ synthesis: '',
124
+ };
125
+
126
+ if (!text || typeof text !== 'string') return { ...empty, parse_error: 'empty' };
127
+
128
+ const fence = text.match(/```json\s*deliberation\s*([\s\S]*?)```/i)
129
+ || text.match(/```json\s*([\s\S]*?)```/);
130
+ if (fence) {
131
+ try {
132
+ const parsed = JSON.parse(fence[1].trim());
133
+ return {
134
+ grounding: parsed.grounding || [],
135
+ satisfaction_with_prior: Number(parsed.satisfaction_with_prior) || 0,
136
+ accepts_prior: !!parsed.accepts_prior,
137
+ objections: Array.isArray(parsed.objections) ? parsed.objections : [],
138
+ questions: Array.isArray(parsed.questions) ? parsed.questions : [],
139
+ revisions: Array.isArray(parsed.revisions) ? parsed.revisions : [],
140
+ verdict: parsed.verdict || 'CONTEST',
141
+ synthesis: parsed.synthesis || '',
142
+ };
143
+ } catch (e) {
144
+ return { ...empty, parse_error: e.message };
145
+ }
146
+ }
147
+
148
+ // Fallback: infer low satisfaction if output mentions objection language
149
+ const lower = text.toLowerCase();
150
+ const hasObjection = /objection|contest|disagree|not satisfied|blocking|evidence required/.test(lower);
151
+ return {
152
+ ...empty,
153
+ satisfaction_with_prior: hasObjection ? 0.2 : 0.5,
154
+ accepts_prior: !hasObjection,
155
+ verdict: hasObjection ? 'CONTEST' : 'ACCEPT',
156
+ synthesis: text.slice(0, 500),
157
+ parse_error: 'no_json_fence',
158
+ };
159
+ }
160
+
161
+ function objectionKey(obj) {
162
+ return `${obj.target_persona || ''}:${(obj.claim || '').slice(0, 80)}`.toLowerCase();
163
+ }
164
+
165
+ /**
166
+ * Compute round status from round log and threshold.
167
+ */
168
+ function computeRoundStatus(roundLog, threshold, priorOpen = []) {
169
+ const open = [...priorOpen];
170
+ const seenClaims = new Set();
171
+ let objectionsRaised = 0;
172
+ let objectionsResolved = 0;
173
+ const personasMustRerun = new Set();
174
+
175
+ for (const entry of roundLog) {
176
+ const parsed = entry.parsed || {};
177
+ if (parsed.objections?.length) {
178
+ for (const obj of parsed.objections) {
179
+ objectionsRaised += 1;
180
+ const key = objectionKey(obj);
181
+ if (obj.blocking && !parsed.accepts_prior) {
182
+ if (!seenClaims.has(key)) {
183
+ seenClaims.add(key);
184
+ open.push({ ...obj, raised_by: entry.persona, round: entry.round });
185
+ }
186
+ if (obj.target_persona) personasMustRerun.add(obj.target_persona);
187
+ }
188
+ }
189
+ }
190
+ if (parsed.accepts_prior && parsed.satisfaction_with_prior >= threshold) {
191
+ objectionsResolved += parsed.objections?.filter((o) => !o.blocking).length || 0;
192
+ } else if (entry.persona) {
193
+ personasMustRerun.add(entry.persona);
194
+ }
195
+ }
196
+
197
+ // Remove resolved: target persona accepted in a later round
198
+ const filteredOpen = open.filter((obj) => {
199
+ const resolved = roundLog.some((e) =>
200
+ e.persona === obj.target_persona
201
+ && e.parsed?.accepts_prior
202
+ && (e.parsed?.satisfaction_with_prior || 0) >= threshold);
203
+ if (resolved) objectionsResolved += 1;
204
+ return !resolved;
205
+ });
206
+
207
+ const blockingCount = filteredOpen.filter((o) => o.blocking).length;
208
+ const repeatClaims = new Set();
209
+ const claimCounts = {};
210
+ for (const o of filteredOpen) {
211
+ const k = objectionKey(o);
212
+ claimCounts[k] = (claimCounts[k] || 0) + 1;
213
+ if (claimCounts[k] >= 2) repeatClaims.add(k);
214
+ }
215
+
216
+ return {
217
+ open_objections: filteredOpen,
218
+ blocking_count: blockingCount,
219
+ repeat_claims: [...repeatClaims],
220
+ personas_must_rerun: [...personasMustRerun],
221
+ consensus_reached: blockingCount === 0 && roundLog.length > 0
222
+ && roundLog.every((e) => (e.parsed?.satisfaction_with_prior || 0) >= threshold || e.parsed?.accepts_prior),
223
+ objections_raised: objectionsRaised,
224
+ objections_resolved: objectionsResolved,
225
+ };
226
+ }
227
+
228
+ function shouldEscalateToRaj(status, config, currentRound) {
229
+ if (status.blocking_count >= 2) return true;
230
+ if (status.repeat_claims.length >= 1) return true;
231
+ if (currentRound >= (config.escalate_to_raj_after_round || 2) && status.blocking_count >= 1) return true;
232
+ return false;
233
+ }
234
+
235
+ /**
236
+ * Resolve deliberation groups for this run.
237
+ */
238
+ function getDeliberationGroups(chain, { full_chain, ideation_chain, experts, mode }) {
239
+ const groups = chain.deliberation_groups || {};
240
+ if (ideation_chain) return groups.ideation || [['meera'], ['noor', 'anuj'], ['arjun'], ['zara', 'priya']];
241
+ if (full_chain) return groups.critique || [['arjun'], ['meera', 'priya'], ['zara']];
242
+ if (mode === 'lite') {
243
+ const lite = groups.lite || [['arjun'], ['meera']];
244
+ const expertSet = new Set(experts || []);
245
+ return lite.map((g) => g.filter((p) => expertSet.has(p))).filter((g) => g.length);
246
+ }
247
+ // MoE subset: one persona per group max, preserve order
248
+ const result = [];
249
+ for (const id of experts || []) result.push([id]);
250
+ return result;
251
+ }
252
+
253
+ function summarizePriorOutputs(personaOutputs, personaIds, { round = 0, isProduceRound = false, limits = {} } = {}) {
254
+ const cap = isProduceRound
255
+ ? (limits.produce ?? 1200)
256
+ : (round > 0 ? (limits.rebuttal ?? 400) : (limits.first ?? 800));
257
+
258
+ const lines = [];
259
+ for (const id of personaIds) {
260
+ const entry = personaOutputs[id];
261
+ if (!entry?.text) continue;
262
+
263
+ // Rebuttal rounds: prefer parsed deliberation summary over full prose
264
+ if (round > 0 && !isProduceRound && entry.deliberation) {
265
+ const d = entry.deliberation;
266
+ const summary = {
267
+ verdict: d.verdict,
268
+ satisfaction: d.satisfaction_with_prior,
269
+ objections: (d.objections || []).slice(0, 3).map((o) => ({
270
+ target: o.target_persona,
271
+ claim: (o.claim || '').slice(0, 120),
272
+ grounded_in: o.grounded_in,
273
+ })),
274
+ revisions: (d.revisions || []).slice(0, 2),
275
+ };
276
+ const snippet = entry.text.replace(/```json\s*deliberation[\s\S]*?```/i, '').trim().slice(0, 180);
277
+ lines.push(`### ${id}\n${JSON.stringify(summary)}\n${snippet}${entry.text.length > 180 ? '…' : ''}`);
278
+ continue;
279
+ }
280
+
281
+ lines.push(`### ${id}\n${entry.text.slice(0, cap)}${entry.text.length > cap ? '…' : ''}`);
282
+ }
283
+ return lines.join('\n\n');
284
+ }
285
+
286
+ /**
287
+ * Build user prompt for review/objection round.
288
+ */
289
+ function buildObjectionPrompt({
290
+ persona,
291
+ contextPack,
292
+ priorOutputs,
293
+ openObjections,
294
+ round,
295
+ maxRounds,
296
+ threshold,
297
+ isProduceRound,
298
+ task,
299
+ routingLine,
300
+ referenceCitations,
301
+ coveredPointsSummary,
302
+ limits = {},
303
+ }) {
304
+ const priorIds = Object.keys(priorOutputs || {}).filter((id) => id !== persona);
305
+ const priorSummary = summarizePriorOutputs(priorOutputs, priorIds, {
306
+ round,
307
+ isProduceRound,
308
+ limits: {
309
+ first: limits.prior_output_chars_first,
310
+ rebuttal: limits.prior_output_chars_rebuttal,
311
+ produce: limits.prior_output_chars_produce,
312
+ },
313
+ });
314
+
315
+ const contextCap = isProduceRound
316
+ ? (limits.context_pack_chars_produce ?? 3000)
317
+ : (limits.context_pack_chars_objection ?? 1500);
318
+
319
+ const modeLine = isProduceRound
320
+ ? 'PRODUCE MODE (final round): synthesize your full output after deliberation.'
321
+ : `REVIEW MODE (round ${round}/${maxRounds}): critique prior output — do NOT rewrite full wireframe/critique essays.`;
322
+
323
+ const parts = [
324
+ modeLine,
325
+ `Satisfaction threshold: ${threshold} — default accepts_prior=false until evidence addresses objections.`,
326
+ '',
327
+ `Task: ${task}`,
328
+ routingLine || '',
329
+ referenceCitations ? `Reference data:\n${referenceCitations}` : '',
330
+ '',
331
+ 'Context pack (cite these in grounding[]):',
332
+ JSON.stringify(contextPack, null, 2).slice(0, contextCap),
333
+ ];
334
+
335
+ if (priorSummary) {
336
+ parts.push('', 'Prior persona outputs (read critically — contest specific claims):', priorSummary);
337
+ }
338
+
339
+ if (openObjections?.length) {
340
+ parts.push('', 'Open objections you must address or extend:', JSON.stringify(openObjections.slice(0, 10), null, 2));
341
+ }
342
+
343
+ if (coveredPointsSummary) {
344
+ parts.push('', coveredPointsSummary);
345
+ }
346
+
347
+ if (!isProduceRound) {
348
+ if (round > 0) {
349
+ parts.push('', `Rebuttal round ${round}/${maxRounds}: address open objections with NEW evidence. Do not repeat prior paragraphs verbatim.`);
350
+ }
351
+ parts.push('', 'Requirements:',
352
+ '- Every objection must include grounded_in citing task_map, PRD, UI region, or DS token.',
353
+ '- Ask at least one contextual question about the real task/UI/PRD.',
354
+ '- Do not write generic handoff paragraphs.',
355
+ '- Do NOT repeat points already covered by prior personas. If you agree, state "agree with <persona> on <point>" and move on.',
356
+ `- HARD OUTPUT CAP: ~${limits.objection_token_cap ?? 600} tokens — lite schema + deliberation JSON only.`,
357
+ DELIBERATION_JSON_HINT);
358
+ } else {
359
+ parts.push('', 'Produce final structured output + deliberation JSON block.', DELIBERATION_JSON_HINT);
360
+ }
361
+
362
+ return parts.filter(Boolean).join('\n');
363
+ }
364
+
365
+ function buildRajPrompt(contextPack, personaOutputs, openObjections, roundLog) {
366
+ return [
367
+ 'STALEMATE — arbitrate unresolved persona disagreements using your 5 product principles.',
368
+ '',
369
+ 'Context:', JSON.stringify(contextPack, null, 2).slice(0, 2000),
370
+ '',
371
+ 'Persona outputs:', summarizePriorOutputs(personaOutputs, Object.keys(personaOutputs || {})),
372
+ '',
373
+ 'Open objections:', JSON.stringify(openObjections, null, 2),
374
+ '',
375
+ 'Round log summary:', JSON.stringify(roundLog.slice(-8).map((r) => ({
376
+ round: r.round, persona: r.persona, verdict: r.parsed?.verdict, blocking: r.parsed?.objections?.filter((o) => o.blocking).length,
377
+ })), null, 2),
378
+ '',
379
+ 'Output mandatory Stalemate Resolution block + deliberation JSON with verdict SHIP|REVISE|BLOCK.',
380
+ DELIBERATION_JSON_HINT,
381
+ ].join('\n');
382
+ }
383
+
384
+ /**
385
+ * Extract score from persona text for digest.prior_scores.
386
+ */
387
+ function extractScore(text) {
388
+ const m = text.match(/(\d+(?:\.\d+)?)\s*\/\s*5/) || text.match(/score[:\s]+(\d+(?:\.\d+)?)/i);
389
+ return m ? Number(m[1]) : null;
390
+ }
391
+
392
+ /**
393
+ * Run full adversarial deliberation loop.
394
+ * @param {object} opts
395
+ * @param {function} opts.callPersona — async (personaId, system, user, opts) => { text, tokens... }
396
+ */
397
+ async function runDeliberation(opts) {
398
+ const {
399
+ chain,
400
+ experts,
401
+ full_chain,
402
+ ideation_chain,
403
+ mode,
404
+ task,
405
+ routing,
406
+ state,
407
+ figma,
408
+ config,
409
+ callPersona,
410
+ loadPersonaContext,
411
+ buildReferencePack,
412
+ noDeliberate = false,
413
+ } = opts;
414
+
415
+ const dConfig = loadDeliberationConfig(config);
416
+ const threshold = opts.satisfaction_threshold ?? dConfig.satisfaction_threshold;
417
+ const maxRounds = opts.max_rounds ?? dConfig.max_rounds;
418
+
419
+ if (noDeliberate || dConfig.default_mode === 'legacy') {
420
+ return runLegacySequential(opts);
421
+ }
422
+
423
+ const contextPack = buildContextPack(state, task, figma);
424
+ const groups = getDeliberationGroups(chain, { full_chain, ideation_chain, experts, mode });
425
+ const checkpoint = opts.resume_checkpoint || state.host_run?.checkpoint || null;
426
+
427
+ const persona_outputs = { ...(checkpoint?.persona_outputs || state.persona_outputs || {}) };
428
+ const round_log = [...(checkpoint?.round_log || [])];
429
+ let open_objections = [...(checkpoint?.open_objections || [])];
430
+ let round = checkpoint?.round ?? 0;
431
+ let raj_escalated = checkpoint?.raj_escalated ?? false;
432
+ let llm_calls = checkpoint?.llm_calls ?? 0;
433
+ let input_tokens_est = checkpoint?.input_tokens_est ?? 0;
434
+ let output_tokens_est = checkpoint?.output_tokens_est ?? 0;
435
+ const effort_log = [...(checkpoint?.effort_log || [])];
436
+ let objections_raised = checkpoint?.objections_raised ?? 0;
437
+ let objections_resolved = checkpoint?.objections_resolved ?? 0;
438
+ let coveredPointsAccumulator = [...(checkpoint?.covered_points || state.covered_points || [])];
439
+
440
+ const routingLine = `Routing: ${routing.problem_type} → ${experts.join(', ')}`;
441
+ const startGroupIndex = checkpoint?.group_index ?? 0;
442
+ const startGroupRound = checkpoint?.group_round ?? 0;
443
+
444
+ // Execute deliberation groups — Raj runs AFTER all groups (never before Zara)
445
+ for (let groupIndex = startGroupIndex; groupIndex < groups.length; groupIndex++) {
446
+ const group = groups[groupIndex];
447
+ let groupRound = groupIndex === startGroupIndex ? startGroupRound : 0;
448
+ while (groupRound <= maxRounds) {
449
+ const isProduceRound = groupRound === maxRounds;
450
+ const personasToRun = group.filter((id) => {
451
+ if (groupRound === 0) return true;
452
+ const status = computeRoundStatus(round_log, threshold, open_objections);
453
+ return status.personas_must_rerun.includes(id) || !round_log.some((e) => e.persona === id && e.parsed?.accepts_prior);
454
+ });
455
+
456
+ if (!personasToRun.length && groupRound > 0) break;
457
+
458
+ const runOne = async (personaId) => {
459
+ const { system, manifest, effort, callProvider, callModel, maxTokens } =
460
+ await loadPersonaContext(personaId, {
461
+ isProduceRound,
462
+ isObjectionRound: !isProduceRound,
463
+ objectionCap: dConfig.objection_token_cap,
464
+ });
465
+
466
+ const referencePack = buildReferencePack ? await buildReferencePack(personaId, task) : null;
467
+
468
+ const coveredSummary = coveredPointsAccumulator.length
469
+ ? dedup.buildCoveredPointsSummaryFromPoints(coveredPointsAccumulator)
470
+ : '';
471
+
472
+ const user = buildObjectionPrompt({
473
+ persona: personaId,
474
+ contextPack,
475
+ priorOutputs: persona_outputs,
476
+ openObjections: open_objections,
477
+ round: groupRound,
478
+ maxRounds,
479
+ threshold,
480
+ isProduceRound,
481
+ task,
482
+ routingLine,
483
+ referenceCitations: referencePack?.citations?.join('\n'),
484
+ coveredPointsSummary: coveredSummary,
485
+ limits: dConfig,
486
+ });
487
+
488
+ let result;
489
+ try {
490
+ result = await callPersona(personaId, system, user, {
491
+ provider: callProvider,
492
+ model: callModel,
493
+ maxTokens: isProduceRound ? maxTokens : Math.min(maxTokens, dConfig.objection_token_cap),
494
+ effort,
495
+ });
496
+ } catch (err) {
497
+ if (err.name === 'HostLlmPendingError') {
498
+ err.checkpoint = {
499
+ group_index: groupIndex,
500
+ group_round: groupRound,
501
+ persona_outputs,
502
+ round_log,
503
+ round,
504
+ open_objections,
505
+ raj_escalated,
506
+ llm_calls,
507
+ input_tokens_est,
508
+ output_tokens_est,
509
+ effort_log,
510
+ objections_raised,
511
+ objections_resolved,
512
+ covered_points: coveredPointsAccumulator,
513
+ };
514
+ }
515
+ throw err;
516
+ }
517
+
518
+ llm_calls += 1;
519
+ input_tokens_est += result.input_tokens_est || 0;
520
+ output_tokens_est += result.output_tokens_est || 0;
521
+ effort_log.push(result.effort_log_entry);
522
+
523
+ const parsed = parseDeliberationOutput(result.text);
524
+ const entry = {
525
+ round: round,
526
+ persona: personaId,
527
+ parsed,
528
+ raw_length: result.text.length,
529
+ at: new Date().toISOString(),
530
+ };
531
+ round_log.push(entry);
532
+
533
+ persona_outputs[personaId] = {
534
+ text: result.text,
535
+ deliberation: parsed,
536
+ at: entry.at,
537
+ accepted: null,
538
+ full_prompt: result.full_prompt || null,
539
+ structured_output: result.structured_output || null,
540
+ };
541
+
542
+ try {
543
+ const covered = dedup.extractCoveredPoints(result.text, personaId);
544
+ if (covered && covered.length) {
545
+ coveredPointsAccumulator.push.apply(coveredPointsAccumulator, covered);
546
+ }
547
+ } catch {
548
+ // dedup unavailable — continue
549
+ }
550
+
551
+ return entry;
552
+ };
553
+
554
+ await Promise.all(personasToRun.map((id) => runOne(id)));
555
+
556
+ round += 1;
557
+ const status = computeRoundStatus(round_log, threshold, open_objections);
558
+ open_objections = status.open_objections;
559
+ objections_raised += status.objections_raised;
560
+ objections_resolved += status.objections_resolved;
561
+
562
+ if (status.consensus_reached) break;
563
+
564
+ groupRound += 1;
565
+ if (groupRound > maxRounds) break;
566
+ }
567
+ }
568
+
569
+ // Raj escalation after ALL deliberation groups (critique: Zara always runs before Raj)
570
+ const preRajStatus = computeRoundStatus(round_log, threshold, open_objections);
571
+ open_objections = preRajStatus.open_objections;
572
+ if (shouldEscalateToRaj(preRajStatus, dConfig, round) && !raj_escalated) {
573
+ raj_escalated = true;
574
+ const rajCtx = await loadPersonaContext('raj', { isProduceRound: true, isObjectionRound: false });
575
+ const rajUser = buildRajPrompt(contextPack, persona_outputs, open_objections, round_log);
576
+ const rajResult = await callPersona('raj', rajCtx.system, rajUser, {
577
+ provider: rajCtx.callProvider,
578
+ model: rajCtx.callModel,
579
+ maxTokens: rajCtx.maxTokens,
580
+ effort: 'hard',
581
+ });
582
+ llm_calls += 1;
583
+ input_tokens_est += rajResult.input_tokens_est || 0;
584
+ output_tokens_est += rajResult.output_tokens_est || 0;
585
+ const rajParsed = parseDeliberationOutput(rajResult.text);
586
+ round_log.push({ round, persona: 'raj', parsed: rajParsed, raw_length: rajResult.text.length, at: new Date().toISOString() });
587
+ persona_outputs.raj = { text: rajResult.text, deliberation: rajParsed, at: new Date().toISOString(), accepted: null };
588
+ open_objections = [];
589
+ }
590
+
591
+ const prior_scores = { ...(state.digest?.prior_scores || {}) };
592
+ for (const [id, out] of Object.entries(persona_outputs)) {
593
+ const score = extractScore(out.text);
594
+ if (score != null) prior_scores[id] = score;
595
+ }
596
+
597
+ const finalStatus = computeRoundStatus(round_log, threshold, []);
598
+
599
+ return {
600
+ persona_outputs,
601
+ covered_points: coveredPointsAccumulator,
602
+ deliberation: {
603
+ round,
604
+ max_rounds: maxRounds,
605
+ satisfaction_threshold: threshold,
606
+ open_objections: finalStatus.open_objections,
607
+ round_log,
608
+ consensus_reached: finalStatus.consensus_reached,
609
+ raj_escalated,
610
+ mode: 'adversarial',
611
+ },
612
+ digest_patch: { prior_scores },
613
+ metrics: {
614
+ llm_calls,
615
+ input_tokens_est,
616
+ output_tokens_est,
617
+ deliberation_rounds: round,
618
+ objections_raised,
619
+ objections_resolved,
620
+ raj_escalations: raj_escalated ? 1 : 0,
621
+ effort_log,
622
+ },
623
+ };
624
+ }
625
+
626
+ /** Legacy sequential pass-the-parcel ( --no-deliberate ) */
627
+ async function runLegacySequential(opts) {
628
+ const {
629
+ experts, task, routing, state, figma, callPersona, loadPersonaContext, buildReferencePack,
630
+ } = opts;
631
+
632
+ const persona_outputs = {};
633
+ let llm_calls = 0;
634
+ let input_tokens_est = 0;
635
+ let output_tokens_est = 0;
636
+ const effort_log = [];
637
+ const contextPack = buildContextPack(state, task, figma);
638
+ const digest = { ...(state.digest || {}) };
639
+
640
+ for (const id of experts) {
641
+ const ctx = await loadPersonaContext(id, { isProduceRound: true, isObjectionRound: false });
642
+ const referencePack = buildReferencePack ? await buildReferencePack(id, task) : null;
643
+ const priorSummary = summarizePriorOutputs(persona_outputs, Object.keys(persona_outputs));
644
+
645
+ const user = [
646
+ `Task: ${task}`,
647
+ figma ? `Figma: ${figma}` : '',
648
+ `Routing: ${routing.problem_type} → ${experts.join(', ')}`,
649
+ referencePack ? `Reference data:\n${referencePack.citations.join('\n')}` : '',
650
+ `Session digest: ${JSON.stringify(digest).slice(0, 1500)}`,
651
+ `Context pack: ${JSON.stringify(contextPack).slice(0, 1500)}`,
652
+ priorSummary ? `Prior persona outputs:\n${priorSummary}` : '',
653
+ 'Produce your structured output block as defined in your card/skill.',
654
+ ].filter(Boolean).join('\n');
655
+
656
+ const result = await callPersona(id, ctx.system, user, {
657
+ provider: ctx.callProvider,
658
+ model: ctx.callModel,
659
+ maxTokens: ctx.maxTokens,
660
+ effort: ctx.effort,
661
+ });
662
+
663
+ llm_calls += 1;
664
+ input_tokens_est += result.input_tokens_est || 0;
665
+ output_tokens_est += result.output_tokens_est || 0;
666
+ effort_log.push(result.effort_log_entry);
667
+ persona_outputs[id] = {
668
+ text: result.text,
669
+ at: new Date().toISOString(),
670
+ accepted: null,
671
+ full_prompt: result.full_prompt || null,
672
+ structured_output: result.structured_output || null,
673
+ };
674
+
675
+ const score = extractScore(result.text);
676
+ if (score != null) {
677
+ digest.prior_scores = { ...(digest.prior_scores || {}), [id]: score };
678
+ }
679
+ }
680
+
681
+ return {
682
+ persona_outputs,
683
+ covered_points: [],
684
+ deliberation: {
685
+ round: 0,
686
+ max_rounds: 0,
687
+ satisfaction_threshold: 0,
688
+ open_objections: [],
689
+ round_log: [],
690
+ consensus_reached: true,
691
+ raj_escalated: false,
692
+ mode: 'legacy',
693
+ },
694
+ digest_patch: { prior_scores: digest.prior_scores || {} },
695
+ metrics: {
696
+ llm_calls,
697
+ input_tokens_est,
698
+ output_tokens_est,
699
+ deliberation_rounds: 0,
700
+ objections_raised: 0,
701
+ objections_resolved: 0,
702
+ raj_escalations: 0,
703
+ effort_log,
704
+ },
705
+ };
706
+ }
707
+
708
+ module.exports = {
709
+ loadDeliberationConfig,
710
+ buildContextPack,
711
+ parseDeliberationOutput,
712
+ computeRoundStatus,
713
+ shouldEscalateToRaj,
714
+ getDeliberationGroups,
715
+ buildObjectionPrompt,
716
+ buildRajPrompt,
717
+ runDeliberation,
718
+ runLegacySequential,
719
+ DELIBERATION_JSON_HINT,
720
+ DEFAULTS,
721
+ };