agentleFS
PackmindHubPackmindHub/skillsight/CLAUDE.md

skillsight

CLAUDE.md9 starsChanged 5 months agoApache-2.0View on GitHub

Root (runs both packages): Hexagonal architecture with three layers: Cross-cutting backend folders outside the hexagon: - backend/src/config/ — Zod-validated env schema (env.ts). - backend/src/middleware/ — Hono middleware (session-auth.ts for cookie JWT, ingestion-auth.ts for OTLP bearer). - backend/src/parsers/ — OTLP and Loki-stream payload parsers, shared by the push endpoint and the pull scheduler. - backend/src/lib/ — small framework-agnostic helpers (event-bus.ts, request-url.ts). Dependency injection is assembled in backend/src/bootstrap/compose.ts. All repositories are injected at startup; use cases are instantiated per-request in route handlers…

  • Reads credentials
  • Commits and pushes

The file

We split this CLAUDE.md into the rules it gives an agent

12 rules. About 0 are common, 1 is distinctive, and 11 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 11

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    Start Loki integration sync scheduler + marketplace-source sync scheduler

    line 55
  2. Very specific:

    After changing the schema, always run bun run migrate:generate from backend/ — do not hand-author migration files or edit …

    line 85
  3. Very specific:

    Run bun run version:sync — this propagates the same version into backend/package.json and frontend/package.json (kept in sync so workspace …

    line 110
  4. Very specific:

    Commit and tag: git commit -am "release 1.2.3" && git tag release/1.2.3 && git push --follow-tags.

    line 111
  5. Very specific:

    Add domain entity/port to domain/

    line 125
  6. Very specific:

    Add use case(s) to application/

    line 126
  7. Very specific:

    Add Drizzle repository to infrastructure/repositories/

    line 127
  8. Very specific:

    Add Hono route handler to http/

    line 129
  9. Very specific:

    Rule of thumb: prefer the primitive over inline Tailwind for buttons/inputs/cards/tables/page headers.

    line 146
  10. Very specific:

    If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.

    line 154
  11. Very specific:

    Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.

    line 155
  12. Very specific:

    After modifying code, run graphify update .

    line 156

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

CLAUDE.md

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

Commands

Root (runs both packages):

bun run dev          # Start backend + frontend with hot reload
bun run build        # Build frontend (Vite) then backend (Bun)
bun run lint         # Biome lint on both packages
bun run typecheck    # TypeScript strict check on all packages
bun run test         # Run tests across all packages

Backend:

cd backend
bun run dev                    # Hot reload server on :4200
bun run migrate:generate       # Generate Drizzle migrations from schema changes
bun run migrate:run            # Run pending migrations
bun test src/path/to/file.test.ts   # Run a single test file

Frontend:

cd frontend
bun run dev          # Vite dev server on :5173 (proxies /api → :4200)
bun run check        # Biome lint + format check (`biome check src`)
bun test src/path/to/file.test.tsx   # Run a single test file

Architecture

Hexagonal architecture with three layers:

  • Domain (backend/src/domain/): Pure entities + repository/gateway port interfaces. No framework deps.
  • Application (backend/src/application/): Use cases that accept port interfaces as constructor args. Business logic lives here.
  • Infrastructure (backend/src/infrastructure/): Drizzle repository implementations, HTTP gateways (Loki, git), crypto utilities, schedulers.
  • HTTP (backend/src/http/): Hono route handlers — thin layer that calls application use cases and serializes responses.

Cross-cutting backend folders outside the hexagon:

  • backend/src/config/ — Zod-validated env schema (env.ts).
  • backend/src/middleware/ — Hono middleware (session-auth.ts for cookie JWT, ingestion-auth.ts for OTLP bearer).
  • backend/src/parsers/ — OTLP and Loki-stream payload parsers, shared by the push endpoint and the pull scheduler.
  • backend/src/lib/ — small framework-agnostic helpers (event-bus.ts, request-url.ts).

Dependency injection is assembled in backend/src/bootstrap/compose.ts. All repositories are injected at startup; use cases are instantiated per-request in route handlers with their dependencies.

Backend startup sequence (backend/src/index.ts):

  1. Validate env (Zod schema in config/env.ts)
  2. Auto-run Drizzle migrations
  3. Seed admin user if users table is empty
  4. Build DI container → start Hono server
  5. Start Loki integration sync scheduler + marketplace-source sync scheduler

Frontend is a React 18 SPA with React Router v7. Auth state lives in AuthContext which calls /api/auth/me on mount. The apiFetch client in lib/api.ts handles credentials and 401 → logout redirect. All API calls go through the namespaced api.* object exported from lib/api.ts.

Sync-status fan-out uses dedicated context providers in frontend/src/context/ (singular — AuthContext, IntegrationsHealthContext, MarketplaceSourcesHealthContext) that wrap AppShell in ProtectedRoute. They poll their respective endpoints and feed status badges in the sidebar. When you add a new background-syncing entity, follow this pattern instead of fetching status inside each component.

Key Technical Choices

  • Runtime & package manager: Bun (not Node/npm). Use bun for all commands.
  • Backend framework: Hono (lightweight, edge-compatible)
  • ORM: Drizzle — schema is the source of truth in backend/src/db/schema.ts. Run migrate:generate after any schema change.
  • Linter/formatter: Biome (not ESLint/Prettier). Config in biome.json per package. Always run via bun run lint (root) or bun run check (frontend) — never npx biome or bunx biome, which fail to resolve the executable in this workspace.
  • CSS: Tailwind CSS v4 via Vite plugin
  • Tests: Bun test runner. Frontend uses happy-dom (configured in test-setup.ts).
  • Auth: JWT sessions (cookie session), separate Bearer tokens for OTLP ingestion

Environment

Copy .env.example to .env and fill in:

  • DATABASE_URL — PostgreSQL connection string
  • JWT_SECRET — min 32 chars
  • ADMIN_EMAIL + ADMIN_PASSWORD_INITIAL — bootstrapped on first empty DB run
  • PUBLIC_BASE_URL — used in onboarding instructions

For local dev with Docker: docker compose -f docker-compose.dev.yml up -d starts PostgreSQL on 0.0.0.0:5432.

Database

Schema: backend/src/db/schema.ts (single file, all tables).
Migrations: backend/src/db/migrations/ (auto-generated by Drizzle, committed to repo).
After changing the schema, always run bun run migrate:generate from backend/ — do not hand-author migration files or edit meta/_journal.json manually.

Why this matters: Drizzle's runtime migrator (drizzle-orm/postgres-js/migrator) decides whether to apply a migration by comparing its journal when value to the max created_at in __drizzle_migrations. If you add a new entry with a when value <= any already-applied migration's when, it will be silently skipped forever (no error, no warning). This bit us once — three migrations had May-2025 when values smuggled into a journal whose head was already in March 2026, so they never ran in production. If you ever must add a migration without migrate:generate, the when value MUST be strictly greater than every existing entry's when. Prefer Date.now() at file-creation time. Also keep migration SQL idempotent (CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS) so re-running on a partially-applied DB is safe.

**Array binding in sql\...`templates:** never writeANY(${jsArray}::text[])orunnest(${jsArray}::text[], ...) — postgres-js flattens the JS array into scalar params, so the cast runs against a single text value and the query throws (malformed array literal). Use Drizzle's query builder with inArray(col, jsArray)fromdrizzle-orminstead (seeDrizzlePluginSkillRepository.deleteByPlugins`). This has bitten us twice in skill-repository methods — both times the failure was silently swallowed by an outer try/catch and left rows in an inconsistent state, not a visible crash.

Features

  • Dashboard (/dashboard, DashboardPage): Default landing view (the / index Navigates here) with activation totals, sparklines, and top-skill/top-plugin breakdowns. The page composes existing endpoints (api.skills.usage, api.skills.table, api.marketplaces.*, api.plugins.*) — there is no dedicated dashboard endpoint, so aggregation happens client-side.
  • Skills (/skills, SkillsTablePage): The core domain — every claude_code.skill_activated event ingested via OTLP is bucketed by skill name and plugin. Skills are derived from events (no manual create); the skills table is upserted on ingest. Status (to_review / approved / removed / denied / ignored) is inherited from the owning plugin's marketplace status — a marketplace marked denied (or approved / ignored) cascades that status down to its plugins and skills via computePluginStatus and propagateStatusFromPlugins. New skills land in to_review by default; bundled built-ins (skillSource === "bundled") display as approved via the defaultBundledStatus helper. denied is purely informational — it labels entities you no longer want active but does not gate anything on the developer's machine.
  • Marketplaces / Marketplace Sources (/marketplaces, MarketplacesPage + marketplace-sources admin): Git-backed plugin registries. Each source has a gitUrl, optional encrypted accessToken, branch, and configurable syncIntervalMs. On sync, the gateway fetches marketplace.json from the git host and upserts marketplace + plugin records. Schedulers run per-source at their configured interval. The Marketplaces page renders the synced catalog (and its Details drawer surfaces "With skills" stats).
  • Plugins (/plugins, PluginsPage): Synced from marketplace sources. Read-only from the UI.
  • Cohorts (/cohorts, CohortsPage): Saved groupings of skills/plugins for cross-tenant comparison. Composite UI lives in frontend/src/components/cohorts/.
  • Live Events (/events, LiveEventsPage): Tail view of recent ingested events for debugging the OTLP pipeline.
  • Integrations (/settings/integrations, pages/settings/IntegrationsPage.tsx): Loki-backed pull ingestion. Each integration has a url, lokiQuery, and syncIntervalMs. The scheduler in infrastructure/scheduler/sync-scheduler.ts polls Loki and feeds events into the same pipeline as OTLP push. Encrypted basic-auth credentials via auth_password_encrypted.
  • Tokens (/tokens, TokensPage): Bearer tokens minted for OTLP ingestion. Stored hashed; revocation via revoked_tokens JTI list. Separate from session JWTs.
  • Onboarding (/onboarding, OnboardingPage): Post-login walkthrough rendered when no ingestion has occurred yet; uses PUBLIC_BASE_URL to show copy-pasteable OTLP setup snippets.
  • Audit Log (/audit, AuditLogPage): All write operations emit audit events.

Releases

The shipped version is sourced from the root package.json version field. It is displayed in the frontend Sidebar (v{version} · self-hosted) and returned by the backend /health endpoint.

To cut a release:

  1. Bump version in the root package.json (e.g. 0.1.0 → 1.2.3) and add a ## 1.2.3 section to Changelog.md describing the changes. release.yml fails if no matching section is found.
  2. Run bun run version:sync — this propagates the same version into backend/package.json and frontend/package.json (kept in sync so workspace tools and the frontend's Vite-bundled import stay consistent).
  3. Commit and tag: git commit -am "release 1.2.3" && git tag release/1.2.3 && git push --follow-tags.
  4. The release.yml workflow triggers on tags matching release/*. It verifies that all three package.json files match the tag's x.y.z, builds and pushes ghcr.io/packmindhub/skills-obs:1.2.3 + :latest, then creates a GitHub Release titled 1.2.3 with the Changelog.md section as its body.

One-time GHCR setup: GHCR packages are created private by default. After the very first successful push, go to GitHub → Packages → skills-obs → "Package settings" → "Change package visibility" → Public. Subsequent pushes inherit this visibility.

To deploy a specific version: SKILLSIGHT_IMAGE=ghcr.io/packmindhub/skills-obs:1.2.3 docker compose up -d (the production docker-compose.yml defaults to :latest).

OTLP Ingestion

The telemetry endpoint (POST /api/v0/telemetry/v1/logs) accepts OTLP HTTP/JSON format. Parsing logic is in backend/src/parsers/. Authenticated via Bearer token (ingestion tokens, not session JWTs). The endpoint responds in OpenTelemetry PartialSuccess format.

Adding a New Feature

Typical flow for a new backend feature:

  1. Add domain entity/port to domain/
  2. Add use case(s) to application/
  3. Add Drizzle repository to infrastructure/repositories/
  4. Wire it in bootstrap/compose.ts
  5. Add Hono route handler to http/
  6. Register the route in app.ts

Frontend: add page to pages/, add API method to lib/api.ts, add route to App.tsx.

For features that call external services: also add a gateway port interface to domain/ports/ and an HTTP gateway implementation to infrastructure/gateways/. Inject the gateway into use cases alongside repositories.

Frontend Design System

Minimal in-house primitives in frontend/src/components/ui/. Compose with cn() (clsx + tailwind-merge) — no Radix, shadcn, or cva.

Folder layout under frontend/src/components/: ui/ holds generic primitives (Button, Table, …); the sibling folders audit/, charts/, cohorts/, integrations/, layout/, marketplaces/, plugins/, skills/ hold domain-specific composites. Put new domain UI in the matching folder; reach for ui/ only when the component is genuinely generic.

  • Tokens: CSS custom properties + @utility classes (btn-primary, badge-*, nav-active, dot-grid) live in src/index.css. Add new tokens there, not inline.
  • Primitives: Button, Input, Select, FormField, Card, PageHeader, Table (+ THead/TBody/TR/TH/TD/EmptyRow). Import from the barrel @/components/ui.
  • Domain primitives: StatusBadge, SearchBar, MultiSelect, StatusFilter, SegmentedControl, Drawer, Menu/MenuItem/ConfirmMenuItem, Sparkline — kept as-is.
  • Shared classes: _styles.ts exports INPUT_BASE, DROPDOWN_PANEL, MENU_ITEM_BASE for primitives that own their own positioning logic (SearchBar, MultiSelect, Menu).
  • Rule of thumb: prefer the primitive over inline Tailwind for buttons/inputs/cards/tables/page headers. Reach for cn() when composing variants. Tests use bun:test + renderToString from react-dom/server (see Button.test.tsx as a template).

graphify

This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.

Rules:

  • For codebase questions, first run graphify query "<question>" when graphify-out/graph.json exists. Use graphify path "<A>" "<B>" for relationships and graphify explain "<concept>" for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
  • If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
  • Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
  • After modifying code, run graphify update . to keep the graph current (AST-only, no API cost).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.