@ronkovic/aad 0.6.1 → 0.6.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronkovic/aad",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Autonomous Agent Development Orchestrator - Multi-agent TDD pipeline powered by Claude",
5
5
  "module": "src/main.ts",
6
6
  "type": "module",
@@ -586,14 +586,14 @@ tr:hover td {
586
586
  <div class="log-entries" id="log-entries"></div>
587
587
  </div>
588
588
 
589
- <script type="module">class f{baseUrl;constructor(t){this.baseUrl=t}async getProgress(){let t=await fetch(`${this.baseUrl}/api/progress`);if(!t.ok)throw Error(`Failed to fetch progress: ${t.statusText}`);return t.json()}async getWorkers(){let t=await fetch(`${this.baseUrl}/api/workers`);if(!t.ok)throw Error(`Failed to fetch workers: ${t.statusText}`);return t.json()}async getTasks(){let t=await fetch(`${this.baseUrl}/api/tasks`);if(!t.ok)throw Error(`Failed to fetch tasks: ${t.statusText}`);return t.json()}async getGraph(){let t=await fetch(`${this.baseUrl}/api/graph`);if(!t.ok)throw Error(`Failed to fetch graph: ${t.statusText}`);return t.json()}async getTimeline(){let t=await fetch(`${this.baseUrl}/api/timeline`);if(!t.ok)throw Error(`Failed to fetch timeline: ${t.statusText}`);return t.json()}async getLogs(){let t=await fetch(`${this.baseUrl}/api/logs`);if(!t.ok)throw Error(`Failed to fetch logs: ${t.statusText}`);let i=await t.json();return Array.isArray(i)?i:i.entries||[]}async getTaskLogs(t){let i=await fetch(`${this.baseUrl}/api/tasks/${t}/logs`);if(!i.ok)throw Error(`Failed to fetch task logs: ${i.statusText}`);return i.json()}}class c{url;onEvent;eventSource=null;status="disconnected";reconnectAttempts=0;reconnectTimer=null;connectionChangeListeners=[];constructor(t,i){this.url=t;this.onEvent=i}connect(){if(this.eventSource)try{this.eventSource.close()}catch(t){}this.eventSource=new EventSource(this.url),this.eventSource.addEventListener("open",()=>{this.reconnectAttempts=0,this.setStatus("connected")}),this.eventSource.addEventListener("heartbeat",()=>{this.setStatus("connected")}),this.eventSource.addEventListener("message",(t)=>{try{let i=JSON.parse(t.data);this.onEvent(i)}catch(i){console.error("SSE parse error:",i)}}),this.eventSource.addEventListener("error",()=>{this.reconnectAttempts++;let t=this.reconnectAttempts>3?"disconnected":"reconnecting";if(this.setStatus(t),this.eventSource)this.eventSource.close();let i=Math.min(this.reconnectAttempts*3000,9000);this.reconnectTimer=setTimeout(()=>{this.connect()},i)})}disconnect(){if(this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;if(this.eventSource){try{this.eventSource.close()}catch(t){}this.eventSource=null}this.setStatus("disconnected")}isConnected(){return this.status==="connected"}getStatus(){return this.status}getReconnectAttempts(){return this.reconnectAttempts}onConnectionChange(t){this.connectionChangeListeners.push(t)}setStatus(t){if(this.status!==t)this.status=t,this.connectionChangeListeners.forEach((i)=>{try{i(t)}catch(n){console.error("Connection change listener error:",n)}})}}class x{state={progress:{pending:0,running:0,completed:0,failed:0,total:0},tasks:[],workers:[],graph:{nodes:[],edges:[]},timeline:{tasks:[]},logs:[],taskPhases:{}};listeners=[];getState(){return{...this.state}}updateProgress(t){this.state.progress=t,this.notify()}updateTasks(t){this.state.tasks=t,this.notify()}updateTask(t,i){let n=this.state.tasks.findIndex((e)=>e.taskId===t);if(n>=0)this.state.tasks[n]={...this.state.tasks[n],...i},this.notify()}updateWorkers(t){this.state.workers=t,this.notify()}updateGraph(t){this.state.graph=t,this.notify()}updateTimeline(t){this.state.timeline=t,this.notify()}addLog(t){if(this.state.logs.unshift(t),this.state.logs.length>200)this.state.logs=this.state.logs.slice(0,200);this.notify()}updateTaskPhase(t,i,n){this.state.taskPhases[t]={phase:i,status:n},this.notify()}subscribe(t){return this.listeners.push(t),()=>{let i=this.listeners.indexOf(t);if(i>=0)this.listeners.splice(i,1)}}notify(){let t=this.getState();this.listeners.forEach((i)=>{try{i(t)}catch(n){console.error("State listener error:",n)}})}}function v(t){let i=t.total||1,n=i>0?Math.round(t.completed/i*100):0,e=document.getElementById("pct-display"),o=document.getElementById("progress-fill"),d=document.getElementById("s-pending"),s=document.getElementById("s-running"),l=document.getElementById("s-completed"),p=document.getElementById("s-failed");if(e)e.textContent=`${n}%`;if(o)o.style.width=`${n}%`;if(d)d.textContent=String(t.pending||0);if(s)s.textContent=String(t.running||0);if(l)l.textContent=String(t.completed||0);if(p)p.textContent=String(t.failed||0)}function E(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}function $(t){let i=document.getElementById("worker-list"),n=document.getElementById("workers-count");if(!i)return;if(n)n.textContent=`(${t.length})`;if(!t.length){i.innerHTML='<div class="empty-msg">No workers</div>';return}i.innerHTML=t.map((e)=>{let o=e.currentTask?`<span class="worker-task">Task: ${E(String(e.currentTask))}</span>`:"";return`
589
+ <script type="module">class z{baseUrl;constructor(t){this.baseUrl=t}async getProgress(){let t=await fetch(`${this.baseUrl}/api/progress`);if(!t.ok)throw Error(`Failed to fetch progress: ${t.statusText}`);return t.json()}async getWorkers(){let t=await fetch(`${this.baseUrl}/api/workers`);if(!t.ok)throw Error(`Failed to fetch workers: ${t.statusText}`);return t.json()}async getTasks(){let t=await fetch(`${this.baseUrl}/api/tasks`);if(!t.ok)throw Error(`Failed to fetch tasks: ${t.statusText}`);return t.json()}async getGraph(){let t=await fetch(`${this.baseUrl}/api/graph`);if(!t.ok)throw Error(`Failed to fetch graph: ${t.statusText}`);return t.json()}async getTimeline(){let t=await fetch(`${this.baseUrl}/api/timeline`);if(!t.ok)throw Error(`Failed to fetch timeline: ${t.statusText}`);return t.json()}async getLogs(){let t=await fetch(`${this.baseUrl}/api/logs`);if(!t.ok)throw Error(`Failed to fetch logs: ${t.statusText}`);let i=await t.json();return Array.isArray(i)?i:i.entries||[]}async getTaskLogs(t){let i=await fetch(`${this.baseUrl}/api/tasks/${t}/logs`);if(!i.ok)throw Error(`Failed to fetch task logs: ${i.statusText}`);return i.json()}}class D{url;onEvent;eventSource=null;status="disconnected";reconnectAttempts=0;reconnectTimer=null;connectionChangeListeners=[];constructor(t,i){this.url=t;this.onEvent=i}connect(){if(this.eventSource)try{this.eventSource.close()}catch(t){}this.eventSource=new EventSource(this.url),this.eventSource.addEventListener("open",()=>{this.reconnectAttempts=0,this.setStatus("connected")}),this.eventSource.addEventListener("heartbeat",()=>{this.setStatus("connected")}),this.eventSource.addEventListener("message",(t)=>{try{let i=JSON.parse(t.data);this.onEvent(i)}catch(i){console.error("SSE parse error:",i)}}),this.eventSource.addEventListener("error",()=>{this.reconnectAttempts++;let t=this.reconnectAttempts>3?"disconnected":"reconnecting";if(this.setStatus(t),this.eventSource)this.eventSource.close();let i=Math.min(this.reconnectAttempts*3000,9000);this.reconnectTimer=setTimeout(()=>{this.connect()},i)})}disconnect(){if(this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;if(this.eventSource){try{this.eventSource.close()}catch(t){}this.eventSource=null}this.setStatus("disconnected")}isConnected(){return this.status==="connected"}getStatus(){return this.status}getReconnectAttempts(){return this.reconnectAttempts}onConnectionChange(t){this.connectionChangeListeners.push(t)}setStatus(t){if(this.status!==t)this.status=t,this.connectionChangeListeners.forEach((i)=>{try{i(t)}catch(d){console.error("Connection change listener error:",d)}})}}class L{state={progress:{pending:0,running:0,completed:0,failed:0,total:0},tasks:[],workers:[],graph:{nodes:[],edges:[]},timeline:{tasks:[]},logs:[],taskPhases:{}};listeners=[];getState(){return{...this.state}}updateProgress(t){this.state.progress=t,this.notify()}updateTasks(t){this.state.tasks=t,this.notify()}updateTask(t,i){let d=this.state.tasks.findIndex((n)=>n.taskId===t);if(d>=0)this.state.tasks[d]={...this.state.tasks[d],...i},this.notify()}updateWorkers(t){this.state.workers=t,this.notify()}updateGraph(t){this.state.graph=t,this.notify()}updateTimeline(t){this.state.timeline=t,this.notify()}addLog(t){if(this.state.logs.unshift(t),this.state.logs.length>200)this.state.logs=this.state.logs.slice(0,200);this.notify()}updateTaskPhase(t,i,d){this.state.taskPhases[t]={phase:i,status:d},this.notify()}subscribe(t){return this.listeners.push(t),()=>{let i=this.listeners.indexOf(t);if(i>=0)this.listeners.splice(i,1)}}notify(){let t=this.getState();this.listeners.forEach((i)=>{try{i(t)}catch(d){console.error("State listener error:",d)}})}}function j(t){let i=t.total||1,d=i>0?Math.round(t.completed/i*100):0,n=document.getElementById("pct-display"),a=document.getElementById("progress-fill"),e=document.getElementById("s-pending"),s=document.getElementById("s-running"),p=document.getElementById("s-completed"),o=document.getElementById("s-failed");if(n)n.textContent=`${d}%`;if(a)a.style.width=`${d}%`;if(e)e.textContent=String(t.pending||0);if(s)s.textContent=String(t.running||0);if(p)p.textContent=String(t.completed||0);if(o)o.textContent=String(t.failed||0)}function q(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}function P(t){let i=document.getElementById("worker-list"),d=document.getElementById("workers-count");if(!i)return;if(d)d.textContent=`(${t.length})`;if(!t.length){i.innerHTML='<div class="empty-msg">No workers</div>';return}i.innerHTML=t.map((n)=>{let a=n.currentTask?`<span class="worker-task">Task: ${q(String(n.currentTask))}</span>`:"";return`
590
590
  <div class="worker-item">
591
- <span class="worker-dot ${e.status}"></span>
592
- <span class="worker-name">${E(String(e.id))}</span>
593
- ${o}
594
- <span class="badge badge-${e.status}" style="margin-left:auto">${e.status}</span>
591
+ <span class="worker-dot ${n.status}"></span>
592
+ <span class="worker-name">${q(String(n.id))}</span>
593
+ ${a}
594
+ <span class="badge badge-${n.status}" style="margin-left:auto">${n.status}</span>
595
595
  </div>
596
- `}).join("")}function y(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}class u{panelEl=null;overlayEl=null;constructor(){this.createPanel(),this.setupEventListeners()}createPanel(){this.overlayEl=document.createElement("div"),this.overlayEl.className="task-detail-overlay",this.overlayEl.style.cssText=`
596
+ `}).join("")}function $(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}class M{panelEl=null;overlayEl=null;constructor(){this.createPanel(),this.setupEventListeners()}createPanel(){this.overlayEl=document.createElement("div"),this.overlayEl.className="task-detail-overlay",this.overlayEl.style.cssText=`
597
597
  display: none;
598
598
  position: fixed;
599
599
  top: 0;
@@ -618,13 +618,13 @@ tr:hover td {
618
618
  overflow-y: auto;
619
619
  z-index: 1001;
620
620
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
621
- `,document.body.appendChild(this.overlayEl),document.body.appendChild(this.panelEl)}setupEventListeners(){this.overlayEl?.addEventListener("click",()=>this.close()),document.addEventListener("keydown",(t)=>{if(t.key==="Escape"&&this.isOpen())this.close()})}show(t,i){if(!this.panelEl||!this.overlayEl)return;let n={red:"Red (Tests)",green:"Green (Implementation)",verify:"Verify",review:"Review",merge:"Merge"},e=i?`<div class="phase-info">
621
+ `,document.body.appendChild(this.overlayEl),document.body.appendChild(this.panelEl)}setupEventListeners(){this.overlayEl?.addEventListener("click",()=>this.close()),document.addEventListener("keydown",(t)=>{if(t.key==="Escape"&&this.isOpen())this.close()})}show(t,i){if(!this.panelEl||!this.overlayEl)return;let d={red:"Red (Tests)",green:"Green (Implementation)",verify:"Verify",review:"Review",merge:"Merge"},n=i?`<div class="phase-info">
622
622
  <strong>Current Phase:</strong>
623
623
  <span class="activity-phase-${i.phase}">
624
- ${y(n[i.phase]||i.phase)}
624
+ ${$(d[i.phase]||i.phase)}
625
625
  (${i.status})
626
626
  </span>
627
- </div>`:"",o=t.startTime?new Date(t.startTime).toLocaleString():"—",d=t.endTime?new Date(t.endTime).toLocaleString():"—",s=this.calculateDuration(t);this.panelEl.innerHTML=`
627
+ </div>`:"",a=t.startTime?new Date(t.startTime).toLocaleString():"—",e=t.endTime?new Date(t.endTime).toLocaleString():"—",s=this.calculateDuration(t);this.panelEl.innerHTML=`
628
628
  <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
629
629
  <h2 style="color: #4fc3f7; margin: 0; font-size: 18px;">Task Details</h2>
630
630
  <button class="close-btn" style="background: #ef5350; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 14px; font-weight: 600;">Close</button>
@@ -633,12 +633,12 @@ tr:hover td {
633
633
  <div style="display: flex; flex-direction: column; gap: 16px;">
634
634
  <div>
635
635
  <div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Task ID</div>
636
- <div style="font-family: monospace; font-size: 13px; color: #e0e0e0;">${y(String(t.taskId))}</div>
636
+ <div style="font-family: monospace; font-size: 13px; color: #e0e0e0;">${$(String(t.taskId))}</div>
637
637
  </div>
638
638
 
639
639
  <div>
640
640
  <div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Title</div>
641
- <div style="font-size: 15px; color: #e0e0e0; font-weight: 500;">${y(t.title||"—")}</div>
641
+ <div style="font-size: 15px; color: #e0e0e0; font-weight: 500;">${$(t.title||"—")}</div>
642
642
  </div>
643
643
 
644
644
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
@@ -652,23 +652,23 @@ tr:hover td {
652
652
  </div>
653
653
  </div>
654
654
 
655
- ${e}
655
+ ${n}
656
656
 
657
657
  <div>
658
658
  <div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Dependencies</div>
659
659
  <div style="font-size: 13px; color: #e0e0e0;">
660
- ${t.dependsOn&&t.dependsOn.length>0?t.dependsOn.map((p)=>`<code style="background: #3a3a3a; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 11px;">${y(String(p))}</code>`).join(" "):"—"}
660
+ ${t.dependsOn&&t.dependsOn.length>0?t.dependsOn.map((o)=>`<code style="background: #3a3a3a; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 11px;">${$(String(o))}</code>`).join(" "):"—"}
661
661
  </div>
662
662
  </div>
663
663
 
664
664
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
665
665
  <div>
666
666
  <div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Start Time</div>
667
- <div style="font-size: 12px; color: #e0e0e0;">${o}</div>
667
+ <div style="font-size: 12px; color: #e0e0e0;">${a}</div>
668
668
  </div>
669
669
  <div>
670
670
  <div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">End Time</div>
671
- <div style="font-size: 12px; color: #e0e0e0;">${d}</div>
671
+ <div style="font-size: 12px; color: #e0e0e0;">${e}</div>
672
672
  </div>
673
673
  </div>
674
674
 
@@ -677,20 +677,20 @@ tr:hover td {
677
677
  <div style="font-size: 13px; color: #66bb6a; font-weight: 600;">${s}</div>
678
678
  </div>`:""}
679
679
  </div>
680
- `;let l=this.panelEl.querySelector(".close-btn");if(l)l.addEventListener("click",()=>this.close());this.overlayEl.style.display="block",this.panelEl.style.display="block"}close(){if(this.overlayEl)this.overlayEl.style.display="none";if(this.panelEl)this.panelEl.style.display="none"}isOpen(){return this.panelEl?.style.display==="block"}calculateDuration(t){if(!t.startTime)return null;let i=new Date(t.startTime).getTime(),e=(t.endTime?new Date(t.endTime).getTime():Date.now())-i,o=Math.floor(e/1000),d=Math.floor(o/60),s=Math.floor(d/60);if(s>0)return`${s}h ${d%60}m`;else if(d>0)return`${d}m ${o%60}s`;else return`${o}s`}}function m(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}function L(t,i,n){if(t.status==="running"){let e=i[t.taskId];if(e){let d={red:"Red (Tests)",green:"Green (Impl)",verify:"Verify",review:"Review",merge:"Merge"}[e.phase]||e.phase,s=e.status==="completed"?"✓ ":e.status==="failed"?"✗ ":"";return`<span class="activity-phase-${e.phase}">${s}${m(d)}</span>`}return'<span class="activity-phase-green">Running</span>'}else if(n==="connected")return'<span class="activity-connected">Connected</span>';else if(n==="reconnecting")return'<span class="activity-reconnecting">Reconnecting...</span>';else return'<span class="activity-disconnected">Disconnected</span>'}var M=new u;function g(t,i,n){let e=document.getElementById("task-tbody"),o=document.getElementById("tasks-count");if(!e)return;if(o)o.textContent=`(${t.length})`;if(!t.length){e.innerHTML='<tr><td colspan="6" class="empty-msg">No tasks</td></tr>';return}e.innerHTML=t.map((d)=>{let s=(d.dependsOn||[]).map((p)=>m(String(p))).join(", ")||"—",l=L(d,i,n);return`
681
- <tr data-task-id="${m(String(d.taskId))}" style="cursor: pointer;">
682
- <td style="font-family:monospace;font-size:11px">${m(String(d.taskId))}</td>
683
- <td>${m(d.title||"—")}</td>
684
- <td><span class="badge badge-${d.status}">${d.status}</span></td>
685
- <td class="activity-cell">${l}</td>
686
- <td class="priority">${d.priority||0}</td>
680
+ `;let p=this.panelEl.querySelector(".close-btn");if(p)p.addEventListener("click",()=>this.close());this.overlayEl.style.display="block",this.panelEl.style.display="block"}close(){if(this.overlayEl)this.overlayEl.style.display="none";if(this.panelEl)this.panelEl.style.display="none"}isOpen(){return this.panelEl?.style.display==="block"}calculateDuration(t){if(!t.startTime)return null;let i=new Date(t.startTime).getTime(),n=(t.endTime?new Date(t.endTime).getTime():Date.now())-i,a=Math.floor(n/1000),e=Math.floor(a/60),s=Math.floor(e/60);if(s>0)return`${s}h ${e%60}m`;else if(e>0)return`${e}m ${a%60}s`;else return`${a}s`}}function h(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}function X(t,i,d){if(t.status==="running"){let n=i[t.taskId];if(n){let e={red:"Red (Tests)",green:"Green (Impl)",verify:"Verify",review:"Review",merge:"Merge"}[n.phase]||n.phase,s=n.status==="completed"?"✓ ":n.status==="failed"?"✗ ":"";return`<span class="activity-phase-${n.phase}">${s}${h(e)}</span>`}return'<span class="activity-phase-green">Running</span>'}else if(d==="connected")return'<span class="activity-connected">Connected</span>';else if(d==="reconnecting")return'<span class="activity-reconnecting">Reconnecting...</span>';else return'<span class="activity-disconnected">Disconnected</span>'}var Y=new M;function S(t,i,d){let n=document.getElementById("task-tbody"),a=document.getElementById("tasks-count");if(!n)return;if(a)a.textContent=`(${t.length})`;if(!t.length){n.innerHTML='<tr><td colspan="6" class="empty-msg">No tasks</td></tr>';return}n.innerHTML=t.map((e)=>{let s=(e.dependsOn||[]).map((o)=>h(String(o))).join(", ")||"—",p=X(e,i,d);return`
681
+ <tr data-task-id="${h(String(e.taskId))}" style="cursor: pointer;">
682
+ <td style="font-family:monospace;font-size:11px">${h(String(e.taskId))}</td>
683
+ <td>${h(e.title||"—")}</td>
684
+ <td><span class="badge badge-${e.status}">${e.status}</span></td>
685
+ <td class="activity-cell">${p}</td>
686
+ <td class="priority">${e.priority||0}</td>
687
687
  <td class="deps">${s}</td>
688
688
  </tr>
689
- `}).join(""),e.querySelectorAll("tr[data-task-id]").forEach((d)=>{d.addEventListener("click",()=>{let s=d.dataset.taskId,l=t.find((p)=>String(p.taskId)===s);if(l){let p=i[l.taskId];M.show(l,p)}})})}function b(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}class h{filters={info:!0,warn:!0,error:!0,service:"",search:""};autoScroll=!0;constructor(){this.setupEventListeners(),this.loadFiltersFromStorage()}setupEventListeners(){document.querySelectorAll(".log-filters input[data-level]").forEach((o)=>{o.addEventListener("change",()=>{let d=o,s=d.dataset.level;this.filters[s]=d.checked,this.saveFiltersToStorage()})});let t=document.getElementById("svc-filter");if(t)t.addEventListener("change",()=>{this.filters.service=t.value,this.saveFiltersToStorage()});let i=document.getElementById("log-search");if(i)i.addEventListener("input",()=>{this.filters.search=i.value,this.saveFiltersToStorage()});let n=document.getElementById("filter-reset");if(n)n.addEventListener("click",()=>{this.resetFilters()});let e=document.getElementById("log-entries");if(e)e.addEventListener("scroll",()=>{let o=e.scrollHeight-e.scrollTop<=e.clientHeight+10;this.autoScroll=o})}renderLog(t,i=!0){let n=document.getElementById("log-entries");if(!n)return;let e=document.createElement("div"),o=t.level||"info";e.className=`log-entry log-${o}`,e.dataset.level=o,e.dataset.service=t.service||"";let d=t.timestamp?new Date(t.timestamp).toLocaleTimeString():"";if(e.innerHTML=`
690
- <span class="log-time">${d}</span>
691
- <span class="log-svc">${b(t.service||"")}</span>
692
- <span>${b(t.message||"")}</span>
693
- `,i)n.insertBefore(e,n.firstChild);else n.appendChild(e);if(this.applyFilterToEntry(e),this.autoScroll&&i)n.scrollTop=0;this.updateLogCount()}renderAllLogs(t){let i=document.getElementById("log-entries");if(!i)return;i.innerHTML="",t.forEach((n)=>this.renderLog(n,!1)),this.updateLogCount(),this.updateServiceFilter(t)}applyFilterToEntry(t){let i=t.dataset.level,n=i?this.filters[i]:!1;if(n&&this.filters.service)n=t.dataset.service===this.filters.service;if(n&&this.filters.search){let e=this.filters.search.toLowerCase();n=(t.textContent?.toLowerCase()||"").includes(e)}t.classList.toggle("log-hidden",!n)}updateLogCount(){let t=document.getElementById("log-entries"),i=document.getElementById("log-count");if(!t||!i)return;let n=t.querySelectorAll(".log-entry").length,e=t.querySelectorAll(".log-entry:not(.log-hidden)").length;i.textContent=`(Showing ${e}/${n})`}updateServiceFilter(t){let i=document.getElementById("svc-filter");if(!i)return;let n=new Set(t.map((o)=>o.service).filter(Boolean)),e=i.value;if(i.innerHTML='<option value="">All Services</option>',n.forEach((o)=>{let d=document.createElement("option");d.value=o,d.textContent=o,i.appendChild(d)}),e)i.value=e}resetFilters(){this.filters={info:!0,warn:!0,error:!0,service:"",search:""},document.querySelectorAll(".log-filters input[data-level]").forEach((n)=>{n.checked=!0});let t=document.getElementById("svc-filter");if(t)t.value="";let i=document.getElementById("log-search");if(i)i.value="";this.saveFiltersToStorage(),this.reapplyFilters()}reapplyFilters(){let t=document.getElementById("log-entries");if(!t)return;t.querySelectorAll(".log-entry").forEach((i)=>{this.applyFilterToEntry(i)}),this.updateLogCount()}loadFiltersFromStorage(){try{let t=localStorage.getItem("aad-log-filters");if(t){let i=JSON.parse(t);this.filters={...this.filters,...i},document.querySelectorAll(".log-filters input[data-level]").forEach((o)=>{let d=o,s=d.dataset.level;d.checked=this.filters[s]!==!1});let n=document.getElementById("svc-filter");if(n&&this.filters.service)n.value=this.filters.service;let e=document.getElementById("log-search");if(e&&this.filters.search)e.value=this.filters.search}}catch(t){console.error("Failed to load filters:",t)}}saveFiltersToStorage(){try{localStorage.setItem("aad-log-filters",JSON.stringify(this.filters))}catch(t){console.error("Failed to save filters:",t)}this.reapplyFilters()}}var z="http://localhost:7333",r=new f(z),a=new x,T=new c(`${z}/events/all`,P),D=new h;a.subscribe((t)=>{v(t.progress),$(t.workers),g(t.tasks,t.taskPhases,T.getStatus())});function P(t){switch(t.type){case"log:entry":a.addLog(t.entry),D.renderLog(t.entry,!0);break;case"progress:updated":a.updateProgress(t.state);break;case"execution:phase:started":a.updateTaskPhase(t.taskId,t.phase,"running");break;case"execution:phase:completed":a.updateTaskPhase(t.taskId,t.phase,"completed");break;case"execution:phase:failed":a.updateTaskPhase(t.taskId,t.phase,"failed");break;case"task:dispatched":case"task:completed":case"task:failed":if(t.task)a.updateTask(t.task.taskId,t.task);O(),G();break;case"worker:idle":case"worker:busy":B();break;case"heartbeat":break}}async function S(){try{let[t,i,n,e,o,d]=await Promise.all([r.getProgress(),r.getWorkers(),r.getTasks(),r.getGraph(),r.getTimeline(),r.getLogs()]);a.updateProgress(t.progress),a.updateWorkers(i.workers),a.updateTasks(n.tasks),a.updateGraph(e),a.updateTimeline(o),d.forEach((s)=>a.addLog(s)),D.renderAllLogs(d)}catch(t){console.error("Failed to load initial data:",t)}}async function O(){try{let t=await r.getProgress();a.updateProgress(t.progress)}catch(t){console.error("Failed to refresh progress:",t)}}async function B(){try{let t=await r.getWorkers();a.updateWorkers(t.workers)}catch(t){console.error("Failed to refresh workers:",t)}}async function G(){try{let t=await r.getTimeline();a.updateTimeline(t)}catch(t){console.error("Failed to refresh timeline:",t)}}S();T.connect();T.onConnectionChange((t)=>{console.log("SSE connection status:",t),g(a.getState().tasks,a.getState().taskPhases,t)});export{a as store,T as sseClient,r as apiClient};
689
+ `}).join(""),n.querySelectorAll("tr[data-task-id]").forEach((e)=>{e.addEventListener("click",()=>{let s=e.dataset.taskId,p=t.find((o)=>String(o.taskId)===s);if(p){let o=i[p.taskId];Y.show(p,o)}})})}var Z={pending:"#9e9e9e",running:"#4fc3f7",completed:"#66bb6a",failed:"#ef5350"};function H(t){let i=document.getElementById("graph-container");if(!i)return;if(i.innerHTML="",!t.nodes.length){i.innerHTML='<div class="empty-msg">No graph data</div>';return}let d=i.clientWidth||600,n=400,a=20,e=Math.ceil(Math.sqrt(t.nodes.length)),s=Math.ceil(t.nodes.length/e),p=d/(e+1),o=n/(s+1),g=new Map;t.nodes.forEach((f,y)=>{let T=y%e,l=Math.floor(y/e);g.set(f.id,{x:(T+1)*p,y:(l+1)*o})});let r=document.createElementNS("http://www.w3.org/2000/svg","svg");r.setAttribute("width",String(d)),r.setAttribute("height",String(n)),r.style.background="#1a1a1a";let v=document.createElementNS("http://www.w3.org/2000/svg","g");v.setAttribute("class","graph-edges"),t.edges.forEach((f)=>{let y=g.get(f.from),T=g.get(f.to);if(!y||!T)return;let l=document.createElementNS("http://www.w3.org/2000/svg","line");l.setAttribute("x1",String(y.x)),l.setAttribute("y1",String(y.y)),l.setAttribute("x2",String(T.x)),l.setAttribute("y2",String(T.y)),l.setAttribute("stroke","#424242"),l.setAttribute("stroke-width","2"),l.setAttribute("marker-end","url(#arrowhead)"),v.appendChild(l)});let N=document.createElementNS("http://www.w3.org/2000/svg","defs"),u=document.createElementNS("http://www.w3.org/2000/svg","marker");u.setAttribute("id","arrowhead"),u.setAttribute("markerWidth","10"),u.setAttribute("markerHeight","10"),u.setAttribute("refX","8"),u.setAttribute("refY","3"),u.setAttribute("orient","auto"),u.setAttribute("markerUnits","strokeWidth");let b=document.createElementNS("http://www.w3.org/2000/svg","polygon");b.setAttribute("points","0 0, 10 3, 0 6"),b.setAttribute("fill","#424242"),u.appendChild(b),N.appendChild(u),r.appendChild(N),r.appendChild(v);let E=document.createElementNS("http://www.w3.org/2000/svg","g");E.setAttribute("class","graph-nodes"),t.nodes.forEach((f)=>{let y=g.get(f.id);if(!y)return;let T=Z[f.status],l=document.createElementNS("http://www.w3.org/2000/svg","circle");l.setAttribute("cx",String(y.x)),l.setAttribute("cy",String(y.y)),l.setAttribute("r",String(a)),l.setAttribute("fill",T),l.setAttribute("stroke","#2d2d2d"),l.setAttribute("stroke-width","2");let c=document.createElementNS("http://www.w3.org/2000/svg","text");c.setAttribute("x",String(y.x)),c.setAttribute("y",String(y.y+4)),c.setAttribute("text-anchor","middle"),c.setAttribute("fill","#1a1a1a"),c.setAttribute("font-size","10"),c.setAttribute("font-weight","600"),c.textContent=f.id.slice(0,3);let W=document.createElementNS("http://www.w3.org/2000/svg","title");W.textContent=`${f.id} (${f.status})`,l.appendChild(W),E.appendChild(l),E.appendChild(c)}),r.appendChild(E),i.appendChild(r)}function J(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}class B{filters={info:!0,warn:!0,error:!0,service:"",search:""};autoScroll=!0;constructor(){this.setupEventListeners(),this.loadFiltersFromStorage()}setupEventListeners(){document.querySelectorAll(".log-filters input[data-level]").forEach((a)=>{a.addEventListener("change",()=>{let e=a,s=e.dataset.level;this.filters[s]=e.checked,this.saveFiltersToStorage()})});let t=document.getElementById("svc-filter");if(t)t.addEventListener("change",()=>{this.filters.service=t.value,this.saveFiltersToStorage()});let i=document.getElementById("log-search");if(i)i.addEventListener("input",()=>{this.filters.search=i.value,this.saveFiltersToStorage()});let d=document.getElementById("filter-reset");if(d)d.addEventListener("click",()=>{this.resetFilters()});let n=document.getElementById("log-entries");if(n)n.addEventListener("scroll",()=>{let a=n.scrollHeight-n.scrollTop<=n.clientHeight+10;this.autoScroll=a})}renderLog(t,i=!0){let d=document.getElementById("log-entries");if(!d)return;let n=document.createElement("div"),a=t.level||"info";n.className=`log-entry log-${a}`,n.dataset.level=a,n.dataset.service=t.service||"";let e=t.timestamp?new Date(t.timestamp).toLocaleTimeString():"";if(n.innerHTML=`
690
+ <span class="log-time">${e}</span>
691
+ <span class="log-svc">${J(t.service||"")}</span>
692
+ <span>${J(t.message||"")}</span>
693
+ `,i)d.insertBefore(n,d.firstChild);else d.appendChild(n);if(this.applyFilterToEntry(n),this.autoScroll&&i)d.scrollTop=0;this.updateLogCount()}renderAllLogs(t){let i=document.getElementById("log-entries");if(!i)return;i.innerHTML="",t.forEach((d)=>this.renderLog(d,!1)),this.updateLogCount(),this.updateServiceFilter(t)}applyFilterToEntry(t){let i=t.dataset.level,d=i?this.filters[i]:!1;if(d&&this.filters.service)d=t.dataset.service===this.filters.service;if(d&&this.filters.search){let n=this.filters.search.toLowerCase();d=(t.textContent?.toLowerCase()||"").includes(n)}t.classList.toggle("log-hidden",!d)}updateLogCount(){let t=document.getElementById("log-entries"),i=document.getElementById("log-count");if(!t||!i)return;let d=t.querySelectorAll(".log-entry").length,n=t.querySelectorAll(".log-entry:not(.log-hidden)").length;i.textContent=`(Showing ${n}/${d})`}updateServiceFilter(t){let i=document.getElementById("svc-filter");if(!i)return;let d=new Set(t.map((a)=>a.service).filter(Boolean)),n=i.value;if(i.innerHTML='<option value="">All Services</option>',d.forEach((a)=>{let e=document.createElement("option");e.value=a,e.textContent=a,i.appendChild(e)}),n)i.value=n}resetFilters(){this.filters={info:!0,warn:!0,error:!0,service:"",search:""},document.querySelectorAll(".log-filters input[data-level]").forEach((d)=>{d.checked=!0});let t=document.getElementById("svc-filter");if(t)t.value="";let i=document.getElementById("log-search");if(i)i.value="";this.saveFiltersToStorage(),this.reapplyFilters()}reapplyFilters(){let t=document.getElementById("log-entries");if(!t)return;t.querySelectorAll(".log-entry").forEach((i)=>{this.applyFilterToEntry(i)}),this.updateLogCount()}loadFiltersFromStorage(){try{let t=localStorage.getItem("aad-log-filters");if(t){let i=JSON.parse(t);this.filters={...this.filters,...i},document.querySelectorAll(".log-filters input[data-level]").forEach((a)=>{let e=a,s=e.dataset.level;e.checked=this.filters[s]!==!1});let d=document.getElementById("svc-filter");if(d&&this.filters.service)d.value=this.filters.service;let n=document.getElementById("log-search");if(n&&this.filters.search)n.value=this.filters.search}}catch(t){console.error("Failed to load filters:",t)}}saveFiltersToStorage(){try{localStorage.setItem("aad-log-filters",JSON.stringify(this.filters))}catch(t){console.error("Failed to save filters:",t)}this.reapplyFilters()}}var Q="http://localhost:7333",x=new z(Q),m=new L,O=new D(`${Q}/events/all`,K),R=new B;m.subscribe((t)=>{j(t.progress),P(t.workers),S(t.tasks,t.taskPhases,O.getStatus()),H(t.graph)});function K(t){switch(t.type){case"log:entry":m.addLog(t.entry),R.renderLog(t.entry,!0);break;case"progress:updated":m.updateProgress(t.state);break;case"execution:phase:started":m.updateTaskPhase(t.taskId,t.phase,"running");break;case"execution:phase:completed":m.updateTaskPhase(t.taskId,t.phase,"completed");break;case"execution:phase:failed":m.updateTaskPhase(t.taskId,t.phase,"failed");break;case"task:dispatched":case"task:completed":case"task:failed":if(t.task)m.updateTask(t.task.taskId,t.task);_(),U(),w();break;case"worker:idle":case"worker:busy":G();break;case"heartbeat":break}}async function V(){try{let[t,i,d,n,a,e]=await Promise.all([x.getProgress(),x.getWorkers(),x.getTasks(),x.getGraph(),x.getTimeline(),x.getLogs()]);m.updateProgress(t.progress),m.updateWorkers(i.workers),m.updateTasks(d.tasks),m.updateGraph(n),m.updateTimeline(a),e.forEach((s)=>m.addLog(s)),R.renderAllLogs(e)}catch(t){console.error("Failed to load initial data:",t)}}async function _(){try{let t=await x.getProgress();m.updateProgress(t.progress)}catch(t){console.error("Failed to refresh progress:",t)}}async function G(){try{let t=await x.getWorkers();m.updateWorkers(t.workers)}catch(t){console.error("Failed to refresh workers:",t)}}async function U(){try{let t=await x.getTimeline();m.updateTimeline(t)}catch(t){console.error("Failed to refresh timeline:",t)}}async function w(){try{let t=await x.getGraph();m.updateGraph(t)}catch(t){console.error("Failed to refresh graph:",t)}}V();O.connect();O.onConnectionChange((t)=>{console.log("SSE connection status:",t),S(m.getState().tasks,m.getState().taskPhases,t)});export{m as store,O as sseClient,x as apiClient};
694
694
  </script>
695
695
  </body>
696
696
  </html>
@@ -115,4 +115,21 @@ describe("installDependencies integration", () => {
115
115
  // The actual fallback is applied at runtime in installDependencies
116
116
  // when yarn is not available in PATH, resolveAvailableCommand returns "npx"
117
117
  });
118
+
119
+ test("constructs correct npx command when fallback is triggered", async () => {
120
+ // Test that when resolveAvailableCommand returns "npx",
121
+ // the spawn args preserve the original command name
122
+ const { resolveAvailableCommand } = await import("../dependency-installer");
123
+
124
+ // Simulate fallback scenario
125
+ const rawCmd = "yarn";
126
+ const args = ["install", "--frozen-lockfile"];
127
+ const resolved = await resolveAvailableCommand("nonexistent-binary-xyz123"); // Will return "npx"
128
+
129
+ // Construct spawn args as done in installDependencies
130
+ const spawnArgs = resolved === "npx" ? ["npx", rawCmd, ...args] : [resolved, ...args];
131
+
132
+ // Should be: ["npx", "yarn", "install", "--frozen-lockfile"]
133
+ expect(spawnArgs).toEqual(["npx", "yarn", "install", "--frozen-lockfile"]);
134
+ });
118
135
  });
@@ -80,10 +80,14 @@ export async function installDependencies(
80
80
 
81
81
  // Resolve command with fallback (e.g., yarn → npx if yarn not found)
82
82
  const cmd = await resolveAvailableCommand(rawCmd!);
83
- logger.info({ cmd, args, cwd: workspace.path }, "Installing dependencies");
83
+
84
+ // If resolved to npx, preserve original command name as first argument
85
+ const spawnArgs = cmd === "npx" ? ["npx", rawCmd!, ...args] : [cmd, ...args];
86
+
87
+ logger.info({ cmd: spawnArgs[0], args: spawnArgs.slice(1), cwd: workspace.path }, "Installing dependencies");
84
88
 
85
89
  // Bun.spawn で実行(default-spawner.ts と同パターン)
86
- const proc = Bun.spawn([cmd, ...args], {
90
+ const proc = Bun.spawn(spawnArgs, {
87
91
  cwd: workspace.path,
88
92
  stdout: "pipe",
89
93
  stderr: "pipe",
@@ -200,7 +200,7 @@ describe("buildTestCommandWithFallback", () => {
200
200
  expect(await buildTestCommandWithFallback(workspace)).toEqual(["./gradlew", "test"]);
201
201
  });
202
202
 
203
- test("returns fallback for unknown test framework", async () => {
203
+ test("returns fallback for unknown test framework with npm", async () => {
204
204
  const workspace: WorkspaceInfo = {
205
205
  path: "/path/to/workspace",
206
206
  language: "unknown",
@@ -209,10 +209,91 @@ describe("buildTestCommandWithFallback", () => {
209
209
  testFramework: "unknown",
210
210
  };
211
211
 
212
- // After fallback implementation, unknown should return npm test
213
212
  expect(await buildTestCommandWithFallback(workspace)).toEqual(["npm", "test"]);
214
213
  });
215
214
 
215
+ test("returns fallback for unknown test framework with bun", async () => {
216
+ const workspace: WorkspaceInfo = {
217
+ path: "/path/to/workspace",
218
+ language: "unknown",
219
+ packageManager: "bun",
220
+ framework: "unknown",
221
+ testFramework: "unknown",
222
+ };
223
+
224
+ expect(await buildTestCommandWithFallback(workspace)).toEqual(["bun", "test"]);
225
+ });
226
+
227
+ test("returns fallback for unknown test framework with yarn", async () => {
228
+ const workspace: WorkspaceInfo = {
229
+ path: "/path/to/workspace",
230
+ language: "unknown",
231
+ packageManager: "yarn",
232
+ framework: "unknown",
233
+ testFramework: "unknown",
234
+ };
235
+
236
+ expect(await buildTestCommandWithFallback(workspace)).toEqual(["yarn", "test"]);
237
+ });
238
+
239
+ test("returns fallback for unknown test framework with pnpm", async () => {
240
+ const workspace: WorkspaceInfo = {
241
+ path: "/path/to/workspace",
242
+ language: "unknown",
243
+ packageManager: "pnpm",
244
+ framework: "unknown",
245
+ testFramework: "unknown",
246
+ };
247
+
248
+ expect(await buildTestCommandWithFallback(workspace)).toEqual(["pnpm", "test"]);
249
+ });
250
+
251
+ test("builds unknown test with yarn fallback when yarn is unavailable", async () => {
252
+ const workspace: WorkspaceInfo = {
253
+ path: "/path",
254
+ language: "unknown",
255
+ packageManager: "yarn",
256
+ framework: "unknown",
257
+ testFramework: "unknown",
258
+ };
259
+
260
+ const originalWhich = Bun.which;
261
+ Bun.which = (cmd: string) => {
262
+ if (cmd === "yarn") return null;
263
+ return originalWhich(cmd);
264
+ };
265
+
266
+ try {
267
+ const result = await buildTestCommandWithFallback(workspace);
268
+ expect(result).toEqual(["npx", "yarn", "test"]);
269
+ } finally {
270
+ Bun.which = originalWhich;
271
+ }
272
+ });
273
+
274
+ test("builds unknown test with pnpm fallback when pnpm is unavailable", async () => {
275
+ const workspace: WorkspaceInfo = {
276
+ path: "/path",
277
+ language: "unknown",
278
+ packageManager: "pnpm",
279
+ framework: "unknown",
280
+ testFramework: "unknown",
281
+ };
282
+
283
+ const originalWhich = Bun.which;
284
+ Bun.which = (cmd: string) => {
285
+ if (cmd === "pnpm") return null;
286
+ return originalWhich(cmd);
287
+ };
288
+
289
+ try {
290
+ const result = await buildTestCommandWithFallback(workspace);
291
+ expect(result).toEqual(["npx", "pnpm", "test"]);
292
+ } finally {
293
+ Bun.which = originalWhich;
294
+ }
295
+ });
296
+
216
297
  test("builds vitest with fallback when yarn is unavailable", async () => {
217
298
  const workspace: WorkspaceInfo = {
218
299
  path: "/path",
@@ -94,8 +94,14 @@ export async function buildTestCommandWithFallback(workspace: WorkspaceInfo): Pr
94
94
  case "unknown": {
95
95
  if (packageManager === "bun") return ["bun", "test"];
96
96
  if (packageManager === "npm") return ["npm", "test"];
97
- if (packageManager === "yarn") return ["yarn", "test"];
98
- if (packageManager === "pnpm") return ["pnpm", "test"];
97
+ if (packageManager === "yarn") {
98
+ const cmd = await resolveAvailableCommand("yarn");
99
+ return cmd === "npx" ? ["npx", "yarn", "test"] : ["yarn", "test"];
100
+ }
101
+ if (packageManager === "pnpm") {
102
+ const cmd = await resolveAvailableCommand("pnpm");
103
+ return cmd === "npx" ? ["npx", "pnpm", "test"] : ["pnpm", "test"];
104
+ }
99
105
  if (packageManager === "uv") return ["uv", "run", "pytest", "-v"];
100
106
  if (packageManager === "poetry") return ["poetry", "run", "pytest", "-v"];
101
107
  return ["npm", "test"];