diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..13d14b2 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,225 @@ +# Gitlock Architecture + +## System Overview + +Gitlock is a repository intelligence platform that applies behavioral code analysis to help engineering teams understand their codebases through git history forensics. It's built as an Elixir umbrella application with four apps. + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ gitlock (umbrella) │ +│ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ gitlock_cli │ │ gitlock_phx │ │ +│ │ (CLI entry) │ │ (Web + DB) │ │ +│ └──────┬───────┘ └────────┬─────────┘ │ +│ │ │ │ +│ │ ┌──────────────┴──────────────┐ │ +│ │ │ gitlock_workflows │ │ +│ │ │ Pipeline model + Executor │ │ +│ │ └──────────────┬──────────────┘ │ +│ │ │ │ +│ └───────────┬───────┘ │ +│ │ │ +│ ┌───────────┴───────────┐ │ +│ │ gitlock_core │ │ +│ │ (analysis engine) │ │ +│ └───────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +## Dependency Direction + +``` +gitlock_cli ──→ gitlock_core + ↑ +gitlock_phx ──→ gitlock_workflows ──→ gitlock_core +``` + +- `gitlock_core` depends on nothing (pure analysis engine) +- `gitlock_workflows` depends on `gitlock_core` (executor maps nodes to use cases) +- `gitlock_phx` depends on `gitlock_workflows` (pipeline model + execution) and `gitlock_core` (direct analysis for landing page demo) +- `gitlock_cli` depends on `gitlock_core` (direct use case invocation) + +--- + +## App Responsibilities + +### gitlock_core — Analysis Engine + +Hexagonal architecture. Pure domain logic for behavioral code analysis. + +``` +gitlock_core/ +├── application/ # Use case orchestrators +│ ├── use_case.ex # Base behaviour + execute/2 macro +│ ├── use_case_factory.ex +│ └── use_cases/ # 7 analysis types +│ +├── domain/ +│ ├── entities/ # Core domain objects (Commit, Author) +│ ├── services/ # Pure analysis algorithms +│ └── values/ # Value objects (Hotspot, FileHistory, etc.) +│ +├── ports/ # Interface contracts (behaviours) +├── adapters/ # Port implementations (git, reporters, complexity) +└── infrastructure/ # Workspace management, adapter registry +``` + +**Public API:** + +```elixir +GitlockCore.investigate(:hotspots, repo_path, options) +# => {:ok, result} | {:error, reason} + +GitlockCore.available_investigations() +# => [:hotspots, :couplings, :knowledge_silos, :code_age, :blast_radius, :coupled_hotspots, :summary] +``` + +Each use case follows: `resolve_dependencies → run_domain_logic → format_result`. + +#### Node Engine Runtime (feature/node-engine branch) + +A more sophisticated execution system exists on `feature/node-engine` inside `gitlock_core/runtime/`. This is a proper DAG execution engine with: + +``` +runtime/ +├── node.ex # Behaviour: metadata/0, execute/3, validate_parameters/1 +├── engine.ex # GenServer: execution lifecycle, async monitoring +├── workflow.ex # Workflow struct, Reactor compilation, n8n-compatible JSON +├── context.ex # Execution context: variables, logging, metrics, temp storage +├── registry.ex # GenServer: node discovery, registration, search +├── validator.ex # Structural validation, cycle detection, port compatibility +├── nodes/ +│ ├── triggers/git_commits.ex # Source: fetches commits via VCS adapter +│ ├── analysis/hotspot.ex # Wraps HotspotDetection domain service +│ ├── analysis/complexity.ex # Wraps DispatchAnalyzer +│ ├── transform/extract_field.ex # Data reshaping between nodes +│ └── output/csv_export.ex # File output +└── runtime_supervisor.ex # Supervises Registry + Engine +``` + +**Key capabilities not in main:** +- True DAG-based data flow (nodes pass data through ports) +- Compiles workflows to Reactor instances for execution +- Node behaviour allows extensible, pluggable analysis nodes +- Execution context with variables, metrics, and logging +- Comprehensive validation (cycle detection, port type checking, orphan detection) +- 161 tests across all runtime modules + +**Status:** This has been moved to `gitlock_workflows/runtime/` and converged with the visual pipeline model. See PROGRESS.md for details. + +### gitlock_workflows — Pipeline Model + Execution + +Visual workflow system, DAG execution engine, and the bridge between them. + +``` +gitlock_workflows/ +├── pipeline.ex # Visual DAG: nodes, edges, ports +├── node.ex / edge.ex / port.ex # Visual model structs +├── node_catalog.ex # Available node types + use_case_key +├── compiler.ex # Pipeline → Runtime.Workflow → Reactor +├── executor.ex # DAG executor: compile, topo-sort, execute with data flow +├── serializer.ex # Pipeline ↔ JSON for DB/UI +├── templates.ex # Pre-built pipeline configurations +├── application.ex # OTP app, starts RuntimeSupervisor +├── runtime_supervisor.ex # Supervises Registry + Engine +└── runtime/ + ├── node.ex # Behaviour: metadata/execute/validate_parameters + ├── engine.ex # GenServer: execution lifecycle, async monitoring + ├── workflow.ex # Workflow struct, Reactor compilation + ├── context.ex # Execution context (variables, logging, metrics) + ├── registry.ex # GenServer: node discovery + registration + ├── validator.ex # Cycle detection, port types, orphan detection + └── nodes/ + ├── triggers/git_commits.ex + ├── analysis/{hotspot,coupling,knowledge_silo,code_age, + │ coupled_hotspot,complexity,summary}.ex + ├── transform/extract_field.ex + └── output/csv_export.ex +``` + +**Execution path:** +Pipeline → `Compiler.to_workflow/2` → Runtime.Workflow → topological sort → execute nodes in order + +The Executor compiles the visual Pipeline into a Runtime Workflow, sorts nodes topologically, +then executes them in dependency order. Data flows through port connections — the git trigger +fetches commits once and all downstream analyzers receive them through the DAG. + +### Converged Architecture + +The visual pipeline model and the runtime engine now live together in `gitlock_workflows`: + +| Layer | Purpose | Key modules | +|-------|---------|-------------| +| **Visual** | UI model, DB persistence | Pipeline, Node, Edge, Port, NodeCatalog, Serializer | +| **Bridge** | Conversion between layers | Compiler (Pipeline → Workflow → Reactor) | +| **Runtime** | DAG execution engine | Engine, Registry, Validator, Context, Workflow | +| **Nodes** | Concrete analysis steps | 10 runtime nodes wrapping gitlock_core domain services | + +### gitlock_phx — Phoenix Web Application + +Web interface, persistence, and user management. + +``` +gitlock_phx/ +├── accounts/ # User auth (phx.gen.auth) +├── pipelines/ +│ ├── saved_pipeline.ex # Ecto schema — pipeline config as JSONB +│ └── pipeline_run.ex # Ecto schema — execution history +├── pipelines.ex # Context — CRUD, hydration, template seeding +│ +└── web/ + ├── live/ + │ ├── analyze_live.ex # Single-URL analysis entry point + │ ├── workflow_live.ex # Visual pipeline builder (SvelteFlow) + │ └── hotspots_preview_live.ex # Landing page demo + └── ... +``` + +**Database schema:** + +``` +pipelines +├── id (bigint, PK) +├── user_id (FK → users, nullable for templates) +├── name (string) +├── description (text) +├── config (jsonb) ← serialized Pipeline struct +├── is_template (boolean) +└── timestamps + +pipeline_runs +├── id (bigint, PK) +├── pipeline_id (FK → pipelines) +├── user_id (FK → users) +├── repo_url (string) +├── status (string: running | completed | failed) +├── results (jsonb) ← node results keyed by node_id +├── error (text) +├── started_at / completed_at (utc_datetime) +└── timestamps +``` + +### gitlock_cli — Command Line Interface + +Thin CLI entry point. Parses arguments, delegates to `gitlock_core` use cases directly. + +--- + +## Key Design Decisions + +### Pipelines stored as JSONB blobs (not normalized) + +The entire pipeline graph is serialized into a single JSONB column. Pipeline structure changes frequently during editing and is always loaded/saved as a unit. + +### Hexagonal architecture in gitlock_core + +Ports define contracts, adapters implement them. Swapping git implementations, adding reporters, or mocking for tests doesn't touch domain logic. + +### Workflow execution via message passing + +The Executor sends `{:pipeline_progress, ...}` messages to a caller PID. This works naturally with LiveView's `handle_info`. For sync mode (CLI/tests), the caller is `nil` and notifications are no-ops. + +### Single source of truth for templates + +`GitlockWorkflows.Templates` defines all pipeline templates. `GitlockPhx.Pipelines.seed_templates!/0` delegates to it. diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..72a0476 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,359 @@ +# Gitlock Design System + +> Style guide, component architecture, and frontend conventions for the Gitlock UI. + +## Stack + +| Layer | Tool | Notes | +| ------------- | --------------------------- | ------------------------------------------ | +| Styling | Tailwind CSS v4 | Utility-first, `@plugin` syntax | +| Components | daisyUI v5 | Semantic class names on top of Tailwind | +| Interactivity | Svelte 5 | Runes mode, compiled via esbuild-svelte | +| Flow Editor | @xyflow/svelte | Node-based workflow canvas | +| Icons | Heroicons (via Phoenix) | `hero-*` classes in HEEx templates | +| Icons (JS) | Lucide Svelte (planned) | For Svelte components needing inline icons | +| Bridge | Phoenix LiveView Hooks | `phx-hook` connects LiveView ↔ Svelte | + +--- + +## Color Palette + +We use daisyUI semantic color tokens so the entire UI respects light/dark theme switching automatically. **Never hardcode hex/oklch values in components.** + +### Semantic Tokens (use these) + +| Token | Purpose | Example class | +| -------------------- | ---------------------------------------- | ------------------------ | +| `base-100/200/300` | Page backgrounds, cards, surfaces | `bg-base-200` | +| `base-content` | Default text on base backgrounds | `text-base-content` | +| `primary` | CTAs, active states, key actions | `btn-primary` | +| `secondary` | Supporting actions, secondary nav | `btn-secondary` | +| `accent` | Highlights, badges, attention-grabbers | `badge-accent` | +| `neutral` | Borders, muted elements, dividers | `border-neutral` | +| `info` | Informational alerts, tooltips | `alert-info` | +| `success` | Completed runs, passing checks | `badge-success` | +| `warning` | Caution states, degraded metrics | `alert-warning` | +| `error` | Failed runs, validation errors | `alert-error` | + +### Analysis-Specific Colors + +For heatmaps, risk scores, and data visualization, define a consistent scale: + +| Risk Level | Tailwind Class | Usage | +| ---------- | ----------------- | ----------------------------- | +| Critical | `text-error` | Hotspot score > 0.8 | +| High | `text-warning` | Hotspot score 0.6–0.8 | +| Medium | `text-info` | Hotspot score 0.3–0.6 | +| Low | `text-success` | Hotspot score < 0.3 | +| None | `text-base-content/50` | No data / not analyzed | + +--- + +## Typography + +Use Tailwind's default font stack. No custom fonts unless we add them intentionally later. + +| Element | Classes | +| -------------- | ------------------------------------------------- | +| Page title | `text-2xl font-bold` | +| Section header | `text-lg font-semibold` | +| Card title | `text-base font-medium` | +| Body text | `text-sm` (default) | +| Small/caption | `text-xs text-base-content/60` | +| Mono/code | `font-mono text-sm` | +| File paths | `font-mono text-xs truncate` | + +--- + +## Spacing & Layout + +### Page Shell + +``` +┌─────────────────────────────────────────────┐ +│ Navbar (fixed top, h-16) │ +├──────────┬──────────────────────────────────┤ +│ Sidebar │ Main Content │ +│ (w-64) │ (flex-1, p-6) │ +│ │ │ +│ │ │ +└──────────┴──────────────────────────────────┘ +``` + +- **Navbar**: `navbar bg-base-100 border-b border-base-300 h-16` +- **Sidebar**: `w-64 bg-base-200 border-r border-base-300 overflow-y-auto` +- **Main content**: `flex-1 overflow-y-auto p-6` +- **Full-bleed pages** (workflow canvas): skip padding, `h-[calc(100vh-4rem)]` + +### Spacing Scale + +Use Tailwind defaults. Prefer `gap-*` on flex/grid over margin: + +| Context | Spacing | +| ----------------- | -------- | +| Between sections | `gap-8` | +| Between cards | `gap-4` | +| Inside cards | `p-4` | +| Between form rows | `gap-3` | +| Inline elements | `gap-2` | +| Icon + text | `gap-1.5`| + +--- + +## Component Patterns + +### daisyUI Components (use in HEEx & Svelte) + +Prefer daisyUI class-based components over building from scratch: + +| Need | daisyUI Component | Class Example | +| ------------------- | -------------------------- | -------------------------------- | +| Buttons | `btn` | `btn btn-primary btn-sm` | +| Cards | `card` | `card bg-base-200 shadow-sm` | +| Forms | `input`, `select`, `label` | `input input-bordered input-sm` | +| Navigation | `menu`, `navbar` | `menu menu-sm` | +| Feedback | `alert`, `toast` | `alert alert-info` | +| Data display | `badge`, `table` | `badge badge-success badge-sm` | +| Loading | `loading` | `loading loading-spinner` | +| Modals | `modal` | `modal` + `dialog` element | +| Tabs | `tabs` | `tabs tabs-bordered` | +| Dropdown | `dropdown` | `dropdown dropdown-end` | +| Tooltip | `tooltip` | `tooltip` + `data-tip` | +| Progress | `progress`, `radial-progress` | `progress progress-primary` | +| Toggle/checkbox | `toggle`, `checkbox` | `toggle toggle-primary toggle-sm`| +| Collapse/accordion | `collapse` | `collapse collapse-arrow` | + +### Button Hierarchy + +| Level | Classes | When | +| --------- | -------------------------------- | ---------------------------------- | +| Primary | `btn btn-primary` | One per page/section, main CTA | +| Secondary | `btn btn-secondary btn-outline` | Supporting actions | +| Ghost | `btn btn-ghost` | Navigation, toolbar, low emphasis | +| Danger | `btn btn-error btn-outline` | Destructive actions | +| Sizes | `btn-xs`, `btn-sm`, `btn-md` | `btn-sm` is default in app chrome | + +--- + +## Svelte Component Architecture + +### Directory Structure + +``` +assets/svelte/ +├── components/ # Reusable UI primitives +│ ├── Badge.svelte +│ ├── EmptyState.svelte +│ ├── MetricCard.svelte +│ ├── RiskBadge.svelte +│ └── FilePathDisplay.svelte +├── workflow/ # Workflow canvas & nodes +│ ├── WorkflowCanvas.svelte +│ ├── nodes/ +│ │ ├── AnalysisNode.svelte +│ │ ├── SourceNode.svelte +│ │ ├── TransformNode.svelte +│ │ └── OutputNode.svelte +│ ├── panels/ +│ │ ├── NodeConfigPanel.svelte +│ │ └── WorkflowToolbar.svelte +│ └── stores.js +├── analysis/ # Analysis result displays +│ ├── HotspotTable.svelte +│ ├── CouplingMatrix.svelte +│ ├── KnowledgeMap.svelte +│ └── ComplexityChart.svelte +├── dashboard/ # Dashboard widgets +│ ├── RepoHealthCard.svelte +│ ├── RecentRunsList.svelte +│ └── RiskOverview.svelte +└── shared/ # Cross-cutting utilities + ├── stores.js # Global Svelte stores + └── utils.js # Formatting, helpers +``` + +### Svelte ↔ LiveView Bridge Pattern + +Every Svelte component that mounts from LiveView follows this pattern: + +```javascript +// In assets/js/hooks/my_hook.js +import MyComponent from "../svelte/path/MyComponent.svelte"; + +export const MyHook = { + mounted() { + const props = JSON.parse(this.el.dataset.props || "{}"); + + this.component = new MyComponent({ + target: this.el, + props: { + ...props, + pushEvent: (event, payload) => this.pushEvent(event, payload), + }, + }); + }, + + updated() { + const props = JSON.parse(this.el.dataset.props || "{}"); + // Update reactive props via Svelte 5 $set or re-mount + }, + + destroyed() { + this.component?.$destroy(); + }, +}; +``` + +```elixir +# In LiveView template +