Add Grype/Snyk to supported formats, settings route, filter bar component, accent theming. Fix dark mode description.
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).RepousesEcto.Adapters.Postgres.lib/bulwark_web/— web layer. Router, endpoint, LiveViews, components.BulwarkWeb(lib/bulwark_web.ex) — macro module that definesuse BulwarkWeb, :live_viewetc.html_helpers/0auto-importsCoreComponents, aliasesLayoutsandPhoenix.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 viaupdate_finding_status/2with PubSub broadcast.Bulwark.Ingestion— pipeline context.ParseJob(Oban worker),Detector(format/tool identification), andParsers(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 inassets/css/app.cssvia@import "tailwindcss"and@sourcedirectives. - 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-xxssize (0.625rem) defined via@themedirective. Semantic colors (red, orange, yellow, green) reserved for severity/status only. Nodark:variant — all classes use dark palette directly. data-tableCSS class — defined inapp.css, used on all Flop.Phoenix.table and inline<table>elements. Sticky header with backdrop blur,divide-y divide-zinc-900on tbody, compactpy-1.5cell padding.- Severity badges — 3-letter labels (CRT/HI/MED/LOW) with
font-mono text-xxsand 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.Schemawith filterable/sortable fields. - esbuild — bundles
assets/js/app.js->priv/static/assets/js/app.js. JS deps go inassets/vendor/and are imported fromapp.js. - Colocated hooks — LiveView JS hooks use
phoenix-colocatedpattern, imported inapp.js. Custom hooks (Sidebar,StatusBar,Settings,FilterBar) are defined inapp.jsand merged with colocated hooks. - Only
app.jsandapp.cssbundles exist. Vendor JS deps must be imported intoapp.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.
FilterBarJS hook enables keyboard shortcut. Accent theming viadata-accentsattribute on<body>, controlled bySettingshook with localStorage persistence.
Key conventions
- HTTP client: use
Req(:reqdep). 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 (fromcore_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>outsidelayouts.ex. - No inline
<script>tags in templates. Write hooks inassets/js/. - Uploaded artifacts go to
priv/uploads/(gitignored), configurable via:upload_dir. - All modules have
@moduledoc, public functions have@docand@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).