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
In this repository4 other files it gives its agents.
- drizzle-migrationsSkill
- packmind-onboardSkill
- packmind-update-playbookSkill
- skill-creatorSkill
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
- Distinctive: line 55
Start Loki integration sync scheduler + marketplace-source sync scheduler
- Very specific: line 85
After changing the schema, always run bun run migrate:generate from backend/ — do not hand-author migration files or edit …
- Very specific: line 110
Run bun run version:sync — this propagates the same version into backend/package.json and frontend/package.json (kept in sync so workspace …
- Very specific: line 111
Commit and tag: git commit -am "release 1.2.3" && git tag release/1.2.3 && git push --follow-tags.
- Very specific: line 125
Add domain entity/port to domain/
- Very specific: line 126
Add use case(s) to application/
- Very specific: line 127
Add Drizzle repository to infrastructure/repositories/
- Very specific: line 129
Add Hono route handler to http/
- Very specific: line 146
Rule of thumb: prefer the primitive over inline Tailwind for buttons/inputs/cards/tables/page headers.
- Very specific: line 154
If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
- Very specific: line 155
Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
- Very specific: line 156
After modifying code, run graphify update .
≈ “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.
Files
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 packagesBackend:
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 fileFrontend:
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 fileArchitecture
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.tsfor cookie JWT,ingestion-auth.tsfor 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):
- Validate env (Zod schema in
config/env.ts) - Auto-run Drizzle migrations
- Seed admin user if
userstable is empty - Build DI container → start Hono server
- 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
bunfor all commands. - Backend framework: Hono (lightweight, edge-compatible)
- ORM: Drizzle — schema is the source of truth in
backend/src/db/schema.ts. Runmigrate:generateafter any schema change. - Linter/formatter: Biome (not ESLint/Prettier). Config in
biome.jsonper package. Always run viabun run lint(root) orbun run check(frontend) — nevernpx biomeorbunx 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 stringJWT_SECRET— min 32 charsADMIN_EMAIL+ADMIN_PASSWORD_INITIAL— bootstrapped on first empty DB runPUBLIC_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/indexNavigates 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 — everyclaude_code.skill_activatedevent ingested via OTLP is bucketed by skill name and plugin. Skills are derived from events (no manual create); theskillstable is upserted on ingest. Status (to_review/approved/removed/denied/ignored) is inherited from the owning plugin's marketplace status — a marketplace markeddenied(orapproved/ignored) cascades that status down to its plugins and skills viacomputePluginStatusandpropagateStatusFromPlugins. New skills land into_reviewby default; bundled built-ins (skillSource === "bundled") display asapprovedvia thedefaultBundledStatushelper.deniedis 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-sourcesadmin): Git-backed plugin registries. Each source has agitUrl, optional encryptedaccessToken,branch, and configurablesyncIntervalMs. On sync, the gateway fetchesmarketplace.jsonfrom 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 infrontend/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 aurl,lokiQuery, andsyncIntervalMs. The scheduler ininfrastructure/scheduler/sync-scheduler.tspolls Loki and feeds events into the same pipeline as OTLP push. Encrypted basic-auth credentials viaauth_password_encrypted. - Tokens (
/tokens,TokensPage): Bearer tokens minted for OTLP ingestion. Stored hashed; revocation viarevoked_tokensJTI list. Separate from session JWTs. - Onboarding (
/onboarding,OnboardingPage): Post-login walkthrough rendered when no ingestion has occurred yet; usesPUBLIC_BASE_URLto 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:
- Bump
versionin the rootpackage.json(e.g.0.1.0→1.2.3) and add a## 1.2.3section toChangelog.mddescribing the changes.release.ymlfails if no matching section is found. - Run
bun run version:sync— this propagates the same version intobackend/package.jsonandfrontend/package.json(kept in sync so workspace tools and the frontend's Vite-bundled import stay consistent). - Commit and tag:
git commit -am "release 1.2.3" && git tag release/1.2.3 && git push --follow-tags. - The
release.ymlworkflow triggers on tags matchingrelease/*. It verifies that all threepackage.jsonfiles match the tag'sx.y.z, builds and pushesghcr.io/packmindhub/skills-obs:1.2.3+:latest, then creates a GitHub Release titled1.2.3with theChangelog.mdsection 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:
- Add domain entity/port to
domain/ - Add use case(s) to
application/ - Add Drizzle repository to
infrastructure/repositories/ - Wire it in
bootstrap/compose.ts - Add Hono route handler to
http/ - 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 +
@utilityclasses (btn-primary,badge-*,nav-active,dot-grid) live insrc/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.tsexportsINPUT_BASE,DROPDOWN_PANEL,MENU_ITEM_BASEfor 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 usebun:test+renderToStringfromreact-dom/server(seeButton.test.tsxas 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. Usegraphify path "<A>" "<B>"for relationships andgraphify 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).
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
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.

