trusty-analyze 0.2.0

Sidecar code-analysis daemon for trusty-search: complexity, smells, quality, facts
Documentation
<script>
  /*
   * Why: Operators want a prioritized worklist of "fix these next" items — the
   * refactor-suggestions endpoint already ranks by severity + impact, so this
   * view just needs to render the queue clearly with severity-driven color.
   * What: Severity filter dropdown, then a vertical card list. Each card shows
   * a severity badge, file+lines, function, refactor type chip, rationale,
   * and the suggested_action body.
   * Test: Set min severity to "high"; expect only high/critical cards.
   */
  import { onMount } from 'svelte';
  import {
    getSelectedIndex,
    getRefactors,
    refreshRefactors
  } from '../state.svelte.js';

  let selected = $derived(getSelectedIndex());
  let refactors = $derived(getRefactors());
  let minSeverity = $state('low');
  let topK = $state(20);

  $effect(() => {
    if (!selected) return;
    refreshRefactors(selected, { minSeverity, topK }).catch(() => {});
  });

  onMount(() => {
    if (selected) refreshRefactors(selected, { minSeverity, topK }).catch(() => {});
  });
</script>

<h1 class="page-title">Refactor Suggestions</h1>

{#if !selected}
  <div class="card"><div class="empty">Select an index in the top bar.</div></div>
{:else}
  <div class="filter-bar">
    <label class="text-xs text-muted">
      Min severity
      <select class="select" bind:value={minSeverity} style="margin-left: 8px; width: 140px">
        <option value="low">low</option>
        <option value="medium">medium</option>
        <option value="high">high</option>
        <option value="critical">critical</option>
      </select>
    </label>
    <label class="text-xs text-muted">
      Top K
      <input
        class="input"
        type="number"
        min="1"
        max="200"
        bind:value={topK}
        style="margin-left: 8px; width: 100px"
      />
    </label>
  </div>

  {#if refactors.length === 0}
    <div class="card"><div class="empty">No suggestions at this severity.</div></div>
  {:else}
    <div class="refactor-grid">
      {#each refactors as r}
        {@const sev = (r.severity || 'low').toString().toLowerCase()}
        <div class="refactor-card">
          <div class="rc-head">
            <span class="badge sev-{sev}">{sev}</span>
            <span class="rc-type">{r.refactor_type || r.type || 'refactor'}</span>
          </div>
          <div class="rc-target text-mono text-xs">
            <strong>{r.function_name || r.symbol || '—'}</strong>
            <span class="text-muted">— {r.file || ''}{r.start_line ? `:${r.start_line}` : ''}{r.end_line ? `–${r.end_line}` : ''}</span>
          </div>
          {#if r.rationale}
            <div class="rc-rationale">{r.rationale}</div>
          {/if}
          {#if r.suggested_action}
            <div class="rc-action">
              <div class="rc-action-label">Suggested action</div>
              <pre>{r.suggested_action}</pre>
            </div>
          {/if}
          {#if r.metrics}
            <div class="rc-meta text-xs text-muted">
              {#if r.metrics.cyclomatic != null}cyclo {r.metrics.cyclomatic}{/if}
              {#if r.metrics.cognitive != null} • cognitive {r.metrics.cognitive}{/if}
              {#if r.metrics.loc != null} • loc {r.metrics.loc}{/if}
            </div>
          {/if}
        </div>
      {/each}
    </div>
  {/if}
{/if}

<style>
  .page-title {
    font-size: var(--trusty-fs-xl);
    margin: 0 0 var(--trusty-space-5) 0;
    font-weight: 600;
  }
  .filter-bar {
    display: flex;
    gap: var(--trusty-space-4);
    align-items: center;
    margin-bottom: var(--trusty-space-4);
  }
  .refactor-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
    gap: var(--trusty-space-4);
  }
  .refactor-card {
    background: var(--trusty-card-bg);
    border: 1px solid var(--trusty-border);
    border-radius: var(--trusty-radius);
    padding: var(--trusty-space-4);
    box-shadow: var(--trusty-shadow-sm);
    display: flex;
    flex-direction: column;
    gap: var(--trusty-space-3);
  }
  .rc-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .rc-type {
    padding: 2px 10px;
    border-radius: 999px;
    background: var(--trusty-accent-soft);
    color: var(--trusty-accent);
    font-size: var(--trusty-fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .rc-target {
    word-break: break-all;
  }
  .rc-rationale {
    color: var(--trusty-text-secondary);
    font-size: var(--trusty-fs-sm);
    line-height: 1.5;
  }
  .rc-action {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--trusty-radius-sm);
    padding: var(--trusty-space-3);
  }
  .rc-action-label {
    font-size: var(--trusty-fs-xs);
    color: var(--trusty-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 6px;
  }
  .rc-action pre {
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--trusty-text-primary);
    font-size: var(--trusty-fs-sm);
  }
  .rc-meta {
    margin-top: auto;
  }
</style>