Files
Christopher Fahlin bd73dc781b docs: update README and CLAUDE.md for current feature set
Add Grype/Snyk to supported formats, settings route, filter bar
component, accent theming. Fix dark mode description.
2026-05-13 21:22:46 -07:00

6.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

mix setup                  # Install deps, create DB, build assets
mix phx.server             # Dev server at localhost:4000
iex -S mix phx.server      # Dev server with IEx shell
docker compose up -d       # Start PostgreSQL (required for dev/test)

mix test                   # Run all tests (auto-creates/migrates DB)
mix test test/path_test.exs          # Single file
mix test test/path_test.exs:42       # Single test by line
mix test --failed                    # Re-run failures only

mix format                 # Format all files
mix precommit              # Pre-commit gate: compile --warning-as-errors, deps.unlock --unused, format, test

Architecture

Phoenix 1.8 app with LiveView 1.1, PostgreSQL (Ecto), Bandit HTTP server.

  • lib/bulwark/ — business logic (domain layer). Repo uses Ecto.Adapters.Postgres.
  • lib/bulwark_web/ — web layer. Router, endpoint, LiveViews, components.
  • BulwarkWeb (lib/bulwark_web.ex) — macro module that defines use BulwarkWeb, :live_view etc. html_helpers/0 auto-imports CoreComponents, aliases Layouts and Phoenix.LiveView.JS.
  • config/config.exs (shared) -> dev.exs/test.exs/prod.exs -> runtime.exs.

Bounded Contexts

  • Bulwark.Security — domain context. Schemas: Scan, Asset, Vulnerability, Finding. CRUD with Flop-powered queries. Assets and Vulnerabilities use upsert (conflict on unique keys). Triage via update_finding_status/2 with PubSub broadcast.
  • Bulwark.Ingestion — pipeline context. ParseJob (Oban worker), Detector (format/tool identification), and Parsers (Trivy, SARIF, CycloneDX, Grype, Snyk) that normalize diverse tool outputs into the Security domain. SPDX BOMs are detected but produce zero findings (BOM-only format). Scans can be cancelled while pending/processing.

Ingestion Pipeline

Upload (on /scans) -> Scan record (pending) -> Oban ParseJob enqueued -> Detector identifies tool/format -> Parser normalizes findings -> Assets/Vulnerabilities upserted -> Findings persisted -> PubSub broadcast -> LiveView updates.

Oban queue :ingestion with concurrency 4. Jobs retry up to 3 times. PubSub topics: "scans" (all scan events), "scan:#{id}" (per-scan), "findings" (triage events).

App Shell & Pages

Dark-mode-only SOC dashboard shell with collapsible sidebar (52px collapsed / 180px expanded on desktop, full-width overlay on mobile). Persistent header with breadcrumbs and system integrity pulse. Status bar footer with uptime/sync timers (JS hooks: Sidebar, StatusBar). Each LiveView assigns active_section to highlight the current nav item. Show pages pass page_title for deeper breadcrumb segments.

Route LiveView Purpose
/ DashboardLive KPI overview: severity counts, recent scans, recent vulns
/scans ScanLive.Index Scan list (Flop) + file upload
/scans/:id ScanLive.Show Scan detail + findings sub-table
/vulnerabilities VulnerabilityLive.Index Vulnerability list (Flop)
/vulnerabilities/:id VulnerabilityLive.Show Vuln detail + references + findings
/findings FindingLive.Index Finding list (Flop) + inline triage actions
/assets AssetLive.Index Asset inventory (Flop)
/assets/:id AssetLive.Show Asset detail + metadata + findings
/settings SettingsLive Accent theming preferences (persisted in localStorage)

Frontend

  • Tailwind CSS v4 — no tailwind.config.js. Config lives in assets/css/app.css via @import "tailwindcss" and @source directives.
  • Dark-mode-only design system — zinc palette, no light mode. Inter for UI text, JetBrains Mono for technical data (loaded via Google Fonts). Custom text-xxs size (0.625rem) defined via @theme directive. Semantic colors (red, orange, yellow, green) reserved for severity/status only. No dark: variant — all classes use dark palette directly.
  • data-table CSS class — defined in app.css, used on all Flop.Phoenix.table and inline <table> elements. Sticky header with backdrop blur, divide-y divide-zinc-900 on tbody, compact py-1.5 cell padding.
  • Severity badges — 3-letter labels (CRT/HI/MED/LOW) with font-mono text-xxs and colored border/bg. Finding status badges use short labels (Open/Ack/Fixed/FP).
  • Flop / Flop.Phoenix — filtering, sorting, pagination for all list pages. Schemas derive Flop.Schema with filterable/sortable fields.
  • esbuild — bundles assets/js/app.js -> priv/static/assets/js/app.js. JS deps go in assets/vendor/ and are imported from app.js.
  • Colocated hooks — LiveView JS hooks use phoenix-colocated pattern, imported in app.js. Custom hooks (Sidebar, StatusBar, Settings, FilterBar) are defined in app.js and merged with colocated hooks.
  • Only app.js and app.css bundles exist. Vendor JS deps must be imported into app.js. Google Fonts loaded via <link> in root layout (exception for web fonts).
  • Shared components in CoreComponents: stat_card/1, severity_badge/1, scan_status/1, finding_status_badge/1, filter_bar/1. Used across all pages.
  • Filter bars — cmd+K styled search + quick-filter pills on vulnerabilities (severity), findings (status), and assets (type) pages. FilterBar JS hook enables keyboard shortcut. Accent theming via data-accents attribute on <body>, controlled by Settings hook with localStorage persistence.

Key conventions

  • HTTP client: use Req (:req dep). Do not add HTTPoison, Tesla, or httpc.
  • LiveView templates must wrap content in <Layouts.app flash={@flash} active_section={@active_section}>.
  • Use <.icon name="hero-x-mark"> for icons (from core_components.ex), not Heroicons modules.
  • Use <.input field={@form[:field]}> for form inputs.
  • Forms: always assign via to_form/2, never pass changesets directly to templates.
  • Streams for unbounded append collections only. Flop-paginated tables use plain lists (bounded by page size).
  • No <.flash_group> outside layouts.ex.
  • No inline <script> tags in templates. Write hooks in assets/js/.
  • Uploaded artifacts go to priv/uploads/ (gitignored), configurable via :upload_dir.
  • All modules have @moduledoc, public functions have @doc and @spec.

AGENTS.md

AGENTS.md at project root contains detailed Phoenix 1.8, Elixir, Ecto, HEEx, and LiveView rules. Read it before making changes — it covers common pitfalls (immutable rebinding, list access, HEEx interpolation syntax, class list syntax, form patterns, stream patterns, test assertions).