From 709639337c7f2605ff793be9412a3b9c96deedb1 Mon Sep 17 00:00:00 2001 From: fedepini Date: Tue, 30 Jun 2026 16:26:36 +0200 Subject: [PATCH] feat: added Claude Code documentation --- .claude/ARCHITECTURE.md | 200 ++++++++++++ .claude/CLAUDE.md | 107 +++++++ .claude/DECISIONS.md | 302 ++++++++++++++++++ .claude/WIDGET_GUIDE.md | 505 +++++++++++++++++++++++++++++++ .claude/agents/widget-creator.md | 255 ++++++++++++++++ 5 files changed, 1369 insertions(+) create mode 100644 .claude/ARCHITECTURE.md create mode 100644 .claude/CLAUDE.md create mode 100644 .claude/DECISIONS.md create mode 100644 .claude/WIDGET_GUIDE.md create mode 100644 .claude/agents/widget-creator.md diff --git a/.claude/ARCHITECTURE.md b/.claude/ARCHITECTURE.md new file mode 100644 index 0000000..ba2223b --- /dev/null +++ b/.claude/ARCHITECTURE.md @@ -0,0 +1,200 @@ +# Architecture overview + +This document is written for Claude Code. It explains the mental model needed to +work in this repository without asking questions about "where does X live" or +"what drives Y". + +--- + +## The core idea in one paragraph + +Krateo frontend is a **configuration-driven shell**. There is almost no hardcoded +UI. Instead, the backend serves Kubernetes Custom Resources (CRs) that describe +*what widgets to render, with what data, and with what actions*. The frontend +fetches those resolved CRs at runtime (it fetches them from the backend who resolves the `resourceRefId` references), and +renders the matching React components. Adding a new page means creating YAML in +Kubernetes — no frontend deploy needed. + +--- + +## Startup sequence + +``` +Browser loads index.html + └─ React app boots, reads public/config/config.json + └─ Fetches INIT endpoint (NavMenu CR from k8s) + └─ Resolves sidebar items → NavMenuItem[] → Route[] + └─ Each Route points to a Page CR + └─ Page CR contains items[] → widget CRs + └─ Each widget CR is rendered by its matching React component +``` + +`public/config/config.json` is **injected at deploy time** and never committed. +Its keys (`AUTHN_API_BASE_URL`, `SNOWPLOW_API_BASE_URL`, `EVENTS_API_BASE_URL`, …) +are the only place API hostnames appear. Never hardcode URLs. + +--- + +## Source tree + +``` +src/ + components/ shared, reusable UI components + examples/ + widgets/ YAML example files — one per widget type + pages/ route-level components + hooks/ custom React hooks + utils/ pure helpers +scripts/ Node/tsx tooling (generate-crds, apply-k8s-yamls…) +docs/ architecture docs, widget guide, ADRs +public/config/ runtime config.json (API base URLs, injected at deploy) +``` + +--- + +## The widget system + +### Every widget has four artifacts + +| Artifact | Location | Purpose | +|---|---|---| +| JSON Schema | `src/widgets//.schema.json` | Single source of truth for the widget's props | +| TypeScript type | `src/widgets//.d.ts` | Auto-generated — never edit | +| YAML example | `src/examples/widgets//.example.yaml` | Applied to k8s for local dev | +| React component | `src/widgets//.tsx` | The renderer | + +The schema is the canonical contract. The TypeScript type is derived from it via +`npm run generate-types`. If you change a prop, change the schema first, then +regenerate. + +### How a widget CR is resolved at runtime + +1. The parent CR contains `items[].resourceRefId: "my-button"`. +2. The frontend calls `GET /call?resource=buttons&name=my-button&namespace=krateo-system`. +3. The response is the Button CR's `.status` object. +4. The Button React component receives that status as props and renders. + +`resourceRefId` is always a Kubernetes resource name. The widget kind determines the API resource path +(e.g. `buttons`, `forms`, `tables`). + +### Widget categories + +**Leaf widgets** (render data, no children): +`BarChart`, `Button`, `EventList`, `LineChart`, `Markdown`, `Paragraph`, +`PieChart`, `YamlViewer` + +**Container widgets** (have `items[]` or `allowedResources`, render children): +`ButtonGroup`, `Column`, `DataGrid`, `FlowChart`, `NavMenu`, `Page`, +`Panel`, `Row`, `TabList` + +**Action-capable widgets** (have an `actions` object with `rest`/`navigate`/`openDrawer`/`openModal`): +`Button`, `Form`, `Panel`, `Table` + +**Infrastructure widgets** (configure the app, not visible to end users): +`NavMenu`, `NavMenuItem`, `Notifications`, `Route`, `RoutesLoader`, `Theme` + +### The actions system + +Widgets that can trigger side effects declare an `actions` object with four +possible action types: + +- `rest` — HTTP call to a Kubernetes endpoint. Supports `requireConfirmation`, + `successMessage`, `errorMessage`, `onSuccessNavigateTo`, `onEventNavigateTo`. +- `navigate` — client-side React Router navigation to a `path` or `resourceRefId`. +- `openDrawer` — opens a side drawer rendering a referenced CR. +- `openModal` — opens a modal dialog rendering a referenced CR. + +`clickActionId` on a Button/Panel refers to the `id` of one of these +actions by name. This is the indirection that lets YAML authors wire up +interactions without touching frontend code. + +### Server-Sent Events + +`EventList` and `Notifications` consume SSE streams. The connection is managed +with `event-source-polyfill`. Topics are passed as widget props (`sseEndpoint`, +`sseTopic`). Never open raw `EventSource` connections outside these two +components. + +--- + +## State management + +| Layer | Tool | What lives here | +|---|---|---| +| Server state | TanStack Query v5 | All CRs fetched from k8s, cached, invalidated | +| Local UI state | `useState` | Form field values, modal open, hover | + +There is no global store for server data. If you find yourself putting API +responses into Redux, that is wrong — use TanStack Query. + +--- + +## Routing + +React Router v7. Routes are **dynamic** — they come from `Route` CRs fetched at +startup, not from a static route config file. The `RoutesLoader` widget is +responsible for fetching and registering them. + +For navigation between pages: use React Router's `` or `useNavigate`. +Never manipulate `window.location` directly. + +--- + +## Styling + +CSS Modules only. Every component has a co-located `ComponentName.module.css`. +Class names are `camelCase` in the CSS file and accessed as +`styles.myClassName` in TSX. + +No Tailwind. No styled-components. No inline `style={{}}` props unless a value +is truly dynamic (e.g. computed pixel width). + +Theming is done via the `Theme` widget, which injects Ant Design design tokens +at runtime. The `mode` field on the Theme CR switches between light and dark. + +--- + +## Charts and graphs + +- Time-series / categorical charts → `echarts-for-react` (BarChart, LineChart, PieChart) +- Kubernetes composition graphs → `reactflow` + `@dagrejs/dagre` for layout (FlowChart) + +Do not introduce other charting libraries. + +--- + +## Icons + +Font Awesome via `@fortawesome/react-fontawesome`. Widget YAML specifies icon +names as strings like `"fa-inbox"` (Font Awesome class name format). The React +components convert this to the correct FA library import. Ant Design +icons are used for some instances (e.g. loading spinner). + +--- + +## Build and tooling + +```bash +npm run dev # start dev server on :4000 +npm run lint # ESLint (flat config) +npm run lint:css # Stylelint on CSS modules +npm run generate-types # generates a TypeScript type from a JSON schema file (requires json-schema-to-typescript) +npm run generate-crds # regenerate CRDs from JSON schemas (requires krateoctl) +npm run apply-all # apply all k8s YAMLs to current cluster context +npm run validate-schemas # validates JSON schemas +npm run update-readme-widgets # updates the docs/widget-api-reference.md page with specs for each widget +npm run apply-examples # apply all k8s YAMLs from the src/examples folder in the current cluster +npm run apply-crds # apply all k8s YAMLs of generated CRDs from the krateoctl output folder in the current cluster +npm run examples # start dev server and applies k8s YAMLs from the src/examples folder +``` + +--- + +## What Claude should NOT do in this repo + +- **Do not hardcode API base URLs.** Always read from `public/config/config.json` + via the existing config hook. +- **Do not add new charting libraries.** Use echarts-for-react or reactflow. +- **Do not write global CSS.** Co-locate CSS Modules with the component. +- **Do not run `npm run apply-all` or `npm run generate-crds` without asking.** + These commands write to a live Kubernetes cluster. \ No newline at end of file diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..66d11f9 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,107 @@ +# Krateo Frontend — Claude Code instructions + +## Project overview + +This is the **Krateo Platform frontend** — a React 19 + TypeScript SPA that renders +a dynamic UI by fetching widget definitions from Kubernetes CRDs at runtime. +Widgets are defined as JSON Schema files, each in its own folder (which follows the path +`src/widgets//` and contains the JSON schema, the `.tsx` implementation and the `.ts` type) and consumed +via a Kubernetes-backed API (snowplow, authn, events services). + +The JSON schema is used as basis for creating a Kubernetes CRD using `krateoctl`. + +Stack: **React 19 · TypeScript 5 · Vite 6 · Ant Design 5 · TanStack Query v5 · +React Router v7 · Vitest · ESLint 9 (flat config) · Stylelint · Docker / nginx** + +## Repository layout + +``` +src/ + components/ shared, reusable UI components + examples/ + widgets/ YAML example files — one per widget type + pages/ route-level components + hooks/ custom React hooks + utils/ pure helpers +scripts/ Node/tsx tooling (generate-crds, apply-k8s-yamls…) +docs/ architecture docs, widget guide, ADRs +public/config/ runtime config.json (API base URLs, injected at deploy) +``` + +## Build and test commands + +```bash +npm run dev # start dev server on :4000 +npm run lint # ESLint (flat config) +npm run lint:css # Stylelint on CSS modules +npm run generate-types # generates a TypeScript type from a JSON schema file (requires json-schema-to-typescript) +npm run generate-crds # regenerate CRDs from JSON schemas (requires krateoctl) +npm run apply-all # apply all k8s YAMLs to current cluster context +npm run validate-schemas # validates JSON schemas +npm run update-readme-widgets # updates the docs/widget-api-reference.md page with specs for each widget +npm run apply-examples # apply all k8s YAMLs from the src/examples folder in the current cluster +npm run apply-crds # apply all k8s YAMLs of generated CRDs from the krateoctl output folder in the current cluster +npm run examples # start dev server and applies k8s YAMLs from the src/examples folder +``` + +**Always run `npm run lint` before proposing a PR.** +Never start the dev server unless explicitly asked. + +## Code conventions + +- All components are **functional** with hooks — no class components. +- CSS is **CSS Modules** (`Component.module.css`) — no inline styles, no Tailwind. +- Imports are sorted: external → internal → relative (enforced by ESLint `import-x`). +- Destructure keys are sorted (enforced by `sort-destructure-keys`). +- Use **TanStack Query** for all server state; never raw `useEffect` + `fetch`. +- Forms use **Ant Design Form** (`antd`) — not React Hook Form. +- Routing is React Router v7 — use ``, `useNavigate`, `useParams`. +- Charts are rendered with **echarts-for-react**. +- Flow diagrams use **reactflow**. +- Icons come from **@fortawesome/react-fontawesome** — do not import from antd icons. + +## Widget system + +Widgets are the core abstraction. Each widget: +1. Has a JSON schema in `src/widgets/.schema.json`. +2. Has a YAML example in `src/examples/widgets/.example.yaml` with an example page in `src/examples/widgets/.menu.yaml`. +3. Gets a generated TypeScript type via `npm run generate-types`. +4. Gets a generated CRD via `npm run generate-crds`. +5. Has a React component definition in `src/widgets/.tsx` +6. Has a Typescript file exporting the component in `src/widgets/.ts` +7. If needed, has a custom CSS module in `src/widgets/.module.css` + +When creating a new widget, always create all artifacts together. +See `docs/WIDGET-GUIDE.md` for the full step-by-step guide. + +## Runtime configuration + +The app reads `public/config/config.json` at startup — never hardcode API URLs. +For local dev, copy `public/config/config.example.json` → `public/config/config.json` +and set the correct `AUTHN_API_BASE_URL`, `SNOWPLOW_API_BASE_URL`, `EVENTS_API_BASE_URL`. + +## Security boundaries + +- **Never edit** `.github/workflows/` without explicit user approval. +- Treat `.env*` files as read-only — never write secrets. +- The `scripts/` directory runs against a live cluster — always confirm before running. +- Do not commit `public/config/config.json` (it is gitignored). + +## TypeScript rules + +- `strict: true` is enforced — no `any` without a comment justifying it. +- Prefer `unknown` over `any` for external data. +- Keep types co-located with the component unless shared across 3+ files, then move to `src/types/`. + +## GitHub integration + +When working on issues or PRs, prefer using the `gh-issue-handler` subagent. +Use conventional commit messages: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`. + +## Useful references + +- Widget API reference: `docs/widgets-api-reference.md` +- Architecture overview: `docs/ARCHITECTURE.md` +- Widget creation guide: `docs/WIDGET-GUIDE.md` +- Architectural decisions: `docs/DECISIONS.md` +- Krateo docs: https://docs.krateo.io \ No newline at end of file diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md new file mode 100644 index 0000000..6ce1465 --- /dev/null +++ b/.claude/DECISIONS.md @@ -0,0 +1,302 @@ +# Architectural decision records + +This file documents *why* key technical choices were made in this codebase. +It exists primarily for Claude Code: when Claude proposes adding a new library, +switching a pattern, or refactoring a system, it should check this file first +to understand whether that direction was already considered and rejected. + +Each record has a status: **active** (the decision stands), **superseded** (replaced +by a later record), or **deprecated** (the pattern is being phased out). + +--- + +## ADR-001 — Configuration-driven UI via Kubernetes CRDs + +**Status:** active + +**Decision:** The frontend renders no hardcoded pages or routes. All UI structure +is described as Kubernetes Custom Resources (CRs) fetched at runtime. The app +reads a bootstrap endpoint on startup, receives a tree of `resourceRefId` +references, and resolves each one by fetching the corresponding CR from the +Kubernetes API. + +**Why:** Krateo is a platform engineering tool — its users are platform teams who +need to customise dashboards, forms, and navigation without touching frontend +code or triggering a frontend deploy. Encoding UI in CRDs gives them GitOps +workflows, RBAC, and versioning for free, using infrastructure they already manage. + +**Consequences:** +- Adding a new page or widget requires YAML, not a PR. +- The frontend has no static route file. Routes come from `Route` CRs. +- Every component must accept its entire data contract as props — no component + fetches its own data from a hardcoded URL. +- Claude must not add hardcoded routes, pages, or API URLs to the codebase. + +--- + +## ADR-002 — Ant Design 5 as the component library + +**Status:** active + +**Decision:** Ant Design (`antd` v5) is the primary component library for all +UI primitives: buttons, forms, tables, modals, drawers, notifications, typography. + +**Why:** Ant Design provides a complete, enterprise-grade component set that +covers every interaction pattern needed by a platform engineering dashboard +(complex forms, data tables, drawer panels, tree selects). The alternative +(building on a headless library like Radix or shadcn/ui) would have required +significant custom implementation for components like Table, Form, and DatePicker, +which are already fully featured in Ant Design. The design token system in v5 +also enables the runtime `Theme` widget to switch colours, spacing, and dark/light +mode without a code change. + +**Consequences:** +- Do not add MUI, Chakra, shadcn/ui, or any other component library. +- Do not import from `@ant-design/icons` — use `@fortawesome/react-fontawesome`. + (FA was chosen because widget authors specify icon names as strings in YAML; + FA's string-to-component mapping is more predictable than Ant Design's.) +- Form state is managed by `antd` Form — do not introduce React Hook Form or + Formik. +- Ant Design's design tokens are the only allowed source of colour variables in + CSS Modules; do not hardcode hex values. + +--- + +## ADR-003 — TanStack Query v5 for all server state + +**Status:** active + +**Decision:** All data fetched from the Kubernetes API is managed by TanStack +Query (`@tanstack/react-query`). Components never fetch data with raw `useEffect` ++ `fetch` or `axios` calls directly. + +**Why:** The widget system requires many concurrent, independent data fetches +(one per widget in the tree). TanStack Query handles deduplication, background +refetching, loading/error states, and cache invalidation without boilerplate. +The alternative — a Redux-based async flow with thunks or sagas — was considered +but rejected because it would require action/reducer/selector scaffolding for +every widget type. TanStack Query also makes optimistic updates straightforward, +which is important for action-capable widgets (Button, Form) that mutate +Kubernetes resources. + +**Consequences:** +- All API hooks live in `src/hooks/`. Name them `useQuery` or + `useMutation`. +- `useEffect` for fetching is a code smell in this repo — flag it in review. + +--- + +## ADR-004 — CSS Modules (no utility-first CSS) + +**Status:** active + +**Decision:** Styles are written as CSS Modules co-located with their component +(`Component.module.css`). Tailwind CSS and other utility-first frameworks are not +used. + +**Why:** The widget library is a shared component system, not an application UI. +CSS Modules enforce encapsulation by design — no class name collisions across +widgets, no global style leaks, no specificity battles. Tailwind was evaluated +but rejected because: (1) it requires a build-time purge step that conflicts with +the dynamic class generation used by some widgets; (2) utility classes in JSX make +the component harder to read when prop-to-class mapping is complex; (3) CSS +variables from Ant Design's token system are easier to consume in plain CSS than +in Tailwind's config. + +**Consequences:** +- Every component has exactly one `.module.css` file. +- Class names are `camelCase` in the CSS file, accessed as `styles.myClassName`. +- No `style={{}}` inline props except for truly dynamic computed values + (e.g. `style={{ width: computedPx }}`). +- No global CSS except for `index.css` (resets and Ant Design token overrides). +- Claude must not add Tailwind to any component or suggest converting to it. + +--- + +## ADR-005 — React Router v7 with dynamic routes + +**Status:** active + +**Decision:** Client-side routing uses React Router v7. Routes are not statically +declared — they are registered at runtime from `Route` CRs fetched on startup +by the `RoutesLoader` widget. + +**Why:** React Router v7 was the natural upgrade path from v6 and provides the +data router model that suits the lazy-loaded widget tree. Dynamic route +registration was chosen over static configuration because route names and paths +are defined by platform teams in YAML — they must be configurable at deploy time. + +**Consequences:** +- There is no `src/routes.tsx` or equivalent static route list. +- Navigation in components uses `` or `useNavigate` — never + `window.location.href` or `window.location.assign`. +- The `path` prop in `Route` CRs is the URL path. Changing it requires updating + the CR in Kubernetes, not a frontend code change. + +--- + +## ADR-006 — Vite as the build tool + +**Status:** active + +**Decision:** The project uses Vite v6 with `@vitejs/plugin-react` for +development and production builds. + +**Why:** Vite's ES module dev server eliminates the full-bundle rebuild on file +save, which matters at this codebase's size. Webpack was the alternative but its +configuration overhead and slower HMR were not justified. Create React App was +never considered — it has been unmaintained since 2023. + +**Consequences:** +- Do not introduce webpack, Parcel, or Rollup configuration. +- Environment variables must be prefixed `VITE_` to be exposed to the client. +- The `public/config/config.json` runtime config pattern exists precisely to + avoid baking environment-specific URLs into the Vite build (which would require + a separate build per environment). Do not replace it with `VITE_` env vars for + API base URLs. + +--- + +## ADR-007 — Font Awesome for icons (not Ant Design icons) + +**Status:** active + +**Decision:** Icons are rendered via `@fortawesome/react-fontawesome` using the +free solid, regular, and brand icon packs. Ant Design's built-in icon library +(`@ant-design/icons`) is not used. + +**Why:** Widget authors specify icons as plain strings in YAML +(e.g. `icon: fa-inbox`). Font Awesome's naming convention maps directly to these +strings, making the YAML authoring experience predictable. Ant Design icons use +PascalCase component imports (``) which cannot be driven by a +runtime string without a custom registry. FA's string-based lookup scales to the +full 2000+ icon set without code changes. + +**Consequences:** +- Icon prop in YAML is always a Font Awesome class name string: `"fa-check"`, + `"fa-rocket"`, etc. +- Components convert the string to the FA icon using the library lookup — see + existing widget components for the pattern. +- Do not add `@ant-design/icons` to any component. +- Do not use emoji as icons. + +--- + +## ADR-008 — echarts-for-react for charts, reactflow for graphs + +**Status:** active + +**Decision:** Data visualisation charts (bar, line, pie) use `echarts-for-react`. +Directed graphs (Kubernetes composition topology) use `reactflow` with +`@dagrejs/dagre` for automatic layout. + +**Why:** ECharts was chosen over Chart.js and Recharts because of its first-class +support for large dataset rendering, animation, and the specific chart types +required (stacked bars, line series with time axes). Recharts was evaluated but +its SVG-based rendering has performance issues at scale. ReactFlow was chosen for +the `FlowChart` widget because it handles node dragging, zoom, custom node +renderers, and minimap out of the box — building the same in plain SVG or D3 +would be a significant maintenance burden. + +**Consequences:** +- Do not introduce Chart.js, Recharts, D3, or Nivo for new chart widgets. +- Do not introduce Cytoscape or vis.js for new graph widgets. +- If a new chart type is not available in ECharts, check the ECharts docs before + proposing a new library. + +--- + +## ADR-009 — Client-side filtering via the `prefix` system + +**Status:** active + +**Decision:** The `Filters` widget communicates with target widgets (Table, +DataGrid) through a shared string identifier called `prefix`. Filters are +evaluated client-side. There is no server-side filtering API. + +**Why:** Krateo's backend serves static CR snapshots — it has no query language +for filtering widget data server-side. Client-side filtering with a shared prefix +key gives YAML authors a declarative way to wire a Filters widget to any number +of target widgets without backend changes. The prefix is intentionally opaque: +it is just a string that must match exactly between the Filters widget and its +targets. + +**Consequences:** +- Filters reset on page reload — this is by design, not a bug. +- Multiple widgets can share the same prefix and will all respond to the same + filter state. +- Server-side pagination is not currently supported by the filter system. Do not + attempt to add server-side filtering hooks to existing widgets without a + dedicated ADR. +- See `docs/filters.md` for the full usage guide. + +--- + +## ADR-010 — Server-Sent Events via `event-source-polyfill` + +**Status:** active + +**Decision:** Real-time event streams from Kubernetes use the SSE protocol, +consumed via `event-source-polyfill`. Only the `EventList` and `Notifications` +widgets open SSE connections. + +**Why:** The Krateo backend pushes Kubernetes events over SSE rather than +WebSockets because SSE is simpler to proxy through nginx (no protocol upgrade), +works over HTTP/1.1, and is sufficient for one-directional event streams. +`event-source-polyfill` is used instead of the native `EventSource` because it +supports custom headers (needed for auth tokens), which the native API does not. + +**Consequences:** +- Do not open raw `EventSource` or WebSocket connections outside `EventList` + and `Notifications`. +- SSE endpoint and topic are passed as widget props (`sseEndpoint`, `sseTopic`), + not hardcoded. +- Do not introduce socket.io or similar WebSocket libraries. + +--- + +## ADR-011 — TypeScript strict mode, no `any` + +**Status:** active + +**Decision:** TypeScript is configured with `strict: true`. The use of `any` is +prohibited without an inline comment explaining why it cannot be avoided. +`unknown` is preferred for external/API data that must be type-narrowed before use. + +**Why:** The widget system relies on generated types that flow from JSON Schema +through to React component props. Any hole in the type chain (caused by `any`) +breaks the guarantee that a component only receives data that matches its schema. +This is especially important because widget props come from Kubernetes API +responses at runtime — type errors that slip through become silent rendering +bugs. + +**Consequences:** +- Props interfaces for components come from generated types in `src/types/generated/`. + Never write a manual interface that duplicates a generated type. +- External API responses should be typed as `unknown` and narrowed with a + validator or type guard before use. +- `@ts-ignore` and `@ts-expect-error` require a comment and a ticket reference. + +--- + +## How to add a new ADR + +When making a significant architectural decision — adding a new library, +changing a core pattern, retiring a system — add a new record to this file. + +Use this template: + +```markdown +## ADR-NNN — Short title + +**Status:** active + +**Decision:** One sentence. + +**Why:** The reasoning. What alternatives were considered and rejected, and why. + +**Consequences:** What this means for day-to-day development. What Claude (and +human developers) must not do as a result. +``` + +Increment NNN from the last record. Do not renumber existing records. \ No newline at end of file diff --git a/.claude/WIDGET_GUIDE.md b/.claude/WIDGET_GUIDE.md new file mode 100644 index 0000000..a7e0c72 --- /dev/null +++ b/.claude/WIDGET_GUIDE.md @@ -0,0 +1,505 @@ +# Widget creation guide + +This guide is written for Claude Code. Follow every step in order. +Skipping a step — especially step 1 — breaks the other steps. + +--- + +## The checklist (complete every item, in this sequence) + +``` +[ ] 1. Write the JSON Schema +[ ] 2. Run generate-types → TypeScript type auto-generated +[ ] 3. Run generate-crds → CRD YAML auto-generated +[ ] 4. Write the YAML example file +[ ] 5. Write the React component + CSS Module +[ ] 6. Run update-readme-widgets → Add a row to docs/widgets-api-reference.md +[ ] 7. Run lint +``` + +--- + +## Naming rules + +| Thing | Convention | Example | +|---|---|---| +| Widget name | `PascalCase` | `StatusBadge` | +| Folder names | `PascalCase` matching widget name | `src/widgets/StatusBadge/` | +| File names | `PascalCase.ts` | `StatusBadge.tsx`, `StatusBadge.module.css` | +| YAML `kind` | Same as widget name | `kind: StatusBadge` | +| YAML `metadata.name` | `kebab-case`, prefixed `example-` | `example-status-badge-basic` | +| k8s resource (plural) | `kebab-case` plural lowercase | `statusbadges` | +| `apiVersion` | always `widgets.templates.krateo.io/v1beta1` | — | + +--- + +## Step 1 — JSON Schema + +**Location:** `src/widgets//.schema.json` + +This is the single source of truth for the widget's contract. +TypeScript types and CRDs are generated from it — never the other way around. + +### Minimal skeleton + +```json +{ + "type": "object", + "additionalProperties": false, + "properties": { + "version": { + "description": "widget version", + "type": "string", + "default": "v1beta1" + }, + "kind": { + "default": "Notifications", + "description": "Notifications renders messages coming from a Kubernetes cluster", + "type": "string" + }, + "spec": { + "type": "object", + "properties": { + "widgetData": { + "type": "object", + "properties": { + "queryParams": { + "description": "list of query parameters to add to the notifications call", + "type": "array", + "items": { + "description": "key-value definition of a specific query parameter", + "type": "object", + "properties": { + "name": { + "description": "the name of the query parameter", + "type": "string" + }, + "value": { + "description": "the value of the query parameter", + "type": "string" + } + }, + "required": ["name", "value"], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "resourcesRefs": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "allowed": { + "type": "boolean" + }, + "apiVersion": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "payload": { + "type": "object" + }, + "resource": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": ["POST", "PUT", "PATCH", "DELETE", "GET"] + }, + "slice": { + "type": "object", + "properties": { + "offset": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "perPage": { + "type": "integer" + }, + "continue": { + "type": "boolean" + } + }, + "required": ["perPage"] + } + }, + "required": ["id"] + } + } + }, + "required": ["allowed", "id"] + }, + "apiRef": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": ["name", "namespace"], + "additionalProperties": false + }, + "widgetDataTemplate": { + "type": "array", + "items": { + "type": "object", + "properties": { + "forPath": { + "type": "string" + }, + "expression": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "required": ["widgetData"], + "additionalProperties": false + } + }, + "required": ["kind", "spec", "version"] +} +``` + +### Rules +- `"x-krateo-widget": true` must be present at the top level — the CRD generator uses it. +- Use `"description"` on every property — it becomes the API reference column. +- For colour tokens use: `"enum": ["blue", "darkBlue", "orange", "gray", "red", "green", "violet"]` +- For icon names use: `"type": "string", "description": "Font Awesome icon name, e.g. fa-check"` +- Required fields must be in `"required"` — do not rely on `"default"` alone. +- Nested objects need their own `"type": "object"` with `"properties"`. +- Do not use `oneOf` or `anyOf` +- Copy and paste without editing the `resourcesRefs`, `apiRef` content + +--- + +## Step 2 — Generate the TypeScript type + +```bash +npm run generate-types +``` + +This writes `src/widgets//.ts`. **Never edit that file manually.** +Re-run this command after every schema change. + +--- + +## Step 3 — Generate the CRD + +```bash +npm run generate-crds +``` + +Output goes to `scripts/krateoctl-output/.crd.yaml`. +Do not apply it to the cluster yet — wait until step 4 is done. + +--- + +## Step 4 — YAML example file + +**Location:** `src/examples/widgets//.example.yaml` + +The YAML example is **applied to the local k8s cluster** during development. +It also doubles as the reference test fixture for human reviewers. + +### Structure of every CR in the file + +```yaml +# +# Target: +# Expected behavior: +kind: StatusBadge +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-status-badge-basic # kebab-case, unique within the file + namespace: krateo-system +spec: + widgetData: + status: healthy + label: "API Gateway" + showIcon: true + resourcesRefs: + items: [] # empty for leaf widgets with no child refs +``` + +### Required conventions + +`spec` always has exactly two keys: +- `widgetData` — the widget's own props (maps 1-to-1 to the schema) +- `resourcesRefs` — child/referenced resources; `items: []` if none + +`resourcesRefs.items[]` shape when you do have references: +```yaml +resourcesRefs: + items: + - id: my-action-ref # matches resourceRefId used in widgetData + apiVersion: widgets.templates.krateo.io/v1beta1 + name: the-target-resource-name # k8s resource name + namespace: krateo-system + resource: pages # k8s resource type (plural lowercase) + verb: GET +``` + +### Examples menu + +**Location:** `src/examples/widgets//.menu.yaml` + +Each example CR should be available to be displayed inside a `Page` widget. + +To do that, for each widget a `.menu.yaml` file is created composed of these elements: +- a `NavMenuItem` widget which references a `Page` widget: the goal is displaying an item in the sidebar for each widget with its own name and icon +- a `Page` widget which references as its `items` children one `Panel` widget for each example CR: the goal is displaying all generated examples with a title. If the widget has the `actions` prop, the `Page` contains a `TabList` widget with two tabs: one contains the examples who do not test actions, the other contains the examples that test the actions. +- a `Panel` widget for each example CR contained in the `.example.yaml` file + +Example: + +```yaml +kind: NavMenuItem +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-barchart-navmenuitem + namespace: krateo-system +spec: + widgetData: + allowedResources: + - pages + resourceRefId: example-barchart-page + label: BarChart + icon: fa-chart-bar + path: /barchart + resourcesRefs: + items: + - id: example-barchart-page + apiVersion: widgets.templates.krateo.io/v1beta1 + name: example-barchart-page + namespace: krateo-system + resource: pages + verb: GET +--- +kind: Page +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-barchart-page + namespace: krateo-system +spec: + widgetData: + allowedResources: + - panels + items: + - resourceRefId: example-barchart-empty-array-panel + resourcesRefs: + items: + - id: example-barchart-empty-array-panel + apiVersion: widgets.templates.krateo.io/v1beta1 + name: example-barchart-empty-array-panel + namespace: krateo-system + resource: panels + verb: GET +--- +kind: Panel +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-barchart-empty-array-panel + namespace: krateo-system +spec: + widgetData: + actions: {} + title: 'Empty dataset' + items: + - resourceRefId: example-barchart-empty-array + resourcesRefs: + items: + - id: example-barchart-empty-array + apiVersion: widgets.templates.krateo.io/v1beta1 + name: example-barchart-empty-array + namespace: krateo-system + resource: barcharts + verb: GET +``` + +### How many examples to write + +Write at least: +- One **minimal** example (only required props) +- One **full** example (all optional props set) +- One **edge-case** example per non-trivial behaviour (e.g. empty state, error state) + +### JQ expressions in YAML + +Widget fields support JQ expressions for dynamic values, wrapped in `${ }`: + +```yaml +widgetData: + label: '${ .widget.status.widgetData.name + " — " + .widget.status.widgetData.status }' +``` + +The expression receives the full CR `.status` as context. Only use this in YAML examples +that specifically test dynamic binding — keep the majority of examples with static values. + +### Applying the examples locally + +```bash +npm run apply-examples # applies only the examples, not all CRDs +# or +npm run apply-all # applies everything (CRDs + examples) +``` + +**Ask the user before running these commands** — they write to a live cluster. + +--- + +## Step 5 — React component + +**Locations:** +- `src/widgets//.tsx` +- `src/widgets//.module.css` + +### Component example + +```tsx +import type { IconProp } from '@fortawesome/fontawesome-svg-core' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Button as AntdButton } from 'antd' +import useApp from 'antd/es/app/useApp' + +import { useHandleAction } from '../../hooks/useHandleActions' +import type { WidgetProps } from '../../types/Widget' + +import type { Button as WidgetType } from './Button.type' + +export type ButtonWidgetData = WidgetType['spec']['widgetData'] + +const Button = ({ resourcesRefs, uid, widget, widgetData }: WidgetProps) => { + const { actions, backgroundColor, clickActionId, icon, label, shape, size, type } = widgetData + + const { notification } = useApp() + const { handleAction, isActionLoading } = useHandleAction() + + const action = Object.values(actions) + .flat() + .find(({ id }) => id === clickActionId) + + const onClick = async () => { + if (!action) { + notification.error({ + description: `The widget definition does not include an action (ID: ${clickActionId})`, + message: 'Error while executing the action', + placement: 'bottomLeft', + }) + + return + } + + await handleAction(action, resourcesRefs, undefined, widget) + } + + const handleClick = (event: React.MouseEvent) => { + event.stopPropagation() + + onClick().catch((error) => { + console.error('Error in button click handler:', error) + }) + } + + return ( +
+ : undefined} + key={uid} + loading={isActionLoading} + onClick={(event) => handleClick(event)} + shape={shape || 'default'} + size={size || 'middle'} + style={{ backgroundColor: backgroundColor || undefined }} + type={type || 'primary'} + > + {label} + +
+ ) +} + +export default Button +``` + +### Component rules + +- Props type comes **directly** from the generated type — no manual prop interfaces. +- Props come from the `widgetData` section of the type (generated from the JSON schema) +- Ant Design is used as basis for the design system: most widgets are Ant Design components wrappers +- CSS lives in the `.module.css` file — no `style={{}}` inline props. +- Icons: FontAwesomeIcon — if possible not Ant Design icons. +- Data fetching: if the component needs server data beyond its own props, use a + TanStack Query hook from `src/hooks/` — never raw `useEffect + fetch`. +- No hardcoded API URLs — read from the config hook if needed. + +### CSS Module skeleton + +```css +.badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 2px 8px; + border-radius: 4px; + font-size: 13px; +} + +.healthy { background-color: var(--color-success-bg); color: var(--color-success); } +.degraded { background-color: var(--color-warning-bg); color: var(--color-warning); } +.unknown { background-color: var(--color-default-bg); color: var(--color-default); } +``` + +## Step 8 — API reference entry + +Use the `update-readme-widgets` command to update the `docs/widgets-api-reference.md` + +--- + +## Step 9 — Verify + +```bash +npm run lint # must pass with no errors +``` + +Fix any lint errors before opening a PR. Do not suppress ESLint rules unless +there is a documented reason in the same commit. + +--- + +## Checklist for action-capable widgets + +If your new widget has an `actions` object (like Button, Form, Panel, Table), +it must support all four action types: `rest`, `navigate`, `openDrawer`, `openModal`. + +Copy the action definitions from an existing widget schema like +`src/widgets/Button/Button.schema.json` rather than writing them from scratch. + +--- + +## Checklist for container widgets + +If your new widget renders children via `items[].resourceRefId`, it must include +`allowedResources` in the schema (see `Row.schema.json` for the pattern). +The component receives resolved child widget components, not raw YAML. +Check how `Panel` or `Row` map `items` to rendered children. \ No newline at end of file diff --git a/.claude/agents/widget-creator.md b/.claude/agents/widget-creator.md new file mode 100644 index 0000000..7e55166 --- /dev/null +++ b/.claude/agents/widget-creator.md @@ -0,0 +1,255 @@ +--- +name: "widget-creator" +description: "Use this agent when the user wants to create a new Krateo frontend widget from a natural language description or from a reference to an existing UI component/library. This includes creating all required artifacts: JSON schema, TypeScript types, React component, CSS module, example YAML files, and documentation updates.\\n\\n\\nContext: The user wants to create a new widget based on an Ant Design component.\\nuser: \"Create a widget from the Step component from the Ant Design library\"\\nassistant: \"I'll use the widget-creator agent to handle this end-to-end.\"\\n\\nThe user is asking to create a complete new widget. Use the widget-creator agent to handle all the steps: schema creation, type generation, component implementation, and docs update.\\n\\n\\n\\n\\nContext: The user describes a custom UI widget they want to add to the platform.\\nuser: \"I need a widget that shows a progress ring with a percentage label and a title underneath\"\\nassistant: \"Let me launch the widget-creator agent to build this widget for you.\"\\n\\nThe user is describing a new widget from scratch. The widget-creator agent will gather requirements, design the schema, and produce all artifacts.\\n\\n\\n\\n\\nContext: The user wants to wrap an existing React Flow or ECharts pattern into a widget.\\nuser: \"Can you create a widget that renders a pie chart using echarts-for-react?\"\\nassistant: \"I'll use the widget-creator agent to scaffold the pie chart widget.\"\\n\\nCreating a new chart widget requires all the standard widget artifacts. Delegate to widget-creator.\\n\\n" +model: inherit +color: red +memory: project +--- + +You are an expert Krateo Platform frontend engineer specializing in building new widgets for the Krateo widget system. You have deep knowledge of React 19, TypeScript 5, Ant Design 5, JSON Schema, Kubernetes CRDs, and the Krateo widget architecture. + +Your mission is to take a natural language description (or a reference to an existing library component) and produce a complete, production-ready Krateo widget with all required artifacts, following the conventions in `docs/WIDGET-GUIDE.md` exactly. + +--- + +## Step-by-step workflow + +### 1. Understand requirements +- Parse the user's prompt carefully. If the request references a library component (e.g., Ant Design `Steps`), read the library's documentation/source to understand its props. +- Identify which props are **required**, which are **optional**, and which have sensible defaults. +- Clarify ambiguous requirements before proceeding. Ask focused questions — one round only. +- Determine if a custom CSS module is needed (layout adjustments, spacing, custom colours beyond Ant Design tokens). + +### 2. Read the widget guide +- Always read `docs/WIDGET-GUIDE.md` at the start of every task to get the latest conventions. +- Also read an existing widget (e.g., `src/widgets/Panel/`) as a reference implementation before writing any code. + +### 3. Design the JSON Schema (`src/widgets//.schema.json`) +- Use JSON Schema draft-07. +- The root object must have `"$schema"`, `"title"`, `"description"`, and `"type": "object"`. +- Map every meaningful prop to a schema property with `"type"`, `"description"`, and where applicable `"enum"`, `"default"`, `"minimum"`, `"maximum"`. +- Mark props that are truly required in the `"required"` array. +- Use `camelCase` for property names. +- Keep the schema minimal but complete — every prop that affects rendering should be configurable. +- Add `"additionalProperties": false`. + +### 4. Generate TypeScript types +- Run: `npm run generate-types -- --schema src/widgets//.schema.json --out src/widgets//.ts` +- Verify the generated file looks correct. Do not manually edit generated types. + +### 5. Create the React component (`src/widgets//.tsx`) +- Functional component only — no classes. +- Import the generated type and use it as the props interface. +- Use Ant Design components where appropriate. +- Use `echarts-for-react` for charts, `reactflow` for flow diagrams. +- Icons from `@fortawesome/react-fontawesome` only — never from `antd` icons. +- Use **TanStack Query** for any data fetching — never raw `useEffect` + `fetch`. +- CSS via CSS Modules (`import styles from './.module.css'`) — no inline styles, no Tailwind. +- Sort destructured props alphabetically. +- Imports sorted: external → internal → relative. +- Export the component as the default export. + +### 6. Create the barrel export (`src/widgets//.ts`) +- Re-export the component: `export { default } from './.tsx';` + +### 7. Create the CSS module (`src/widgets//.module.css`) — if needed +- Only create this file if custom styles are required beyond what Ant Design provides. +- Use BEM-lite class naming (`.container`, `.header`, `.item--active`). +- No hardcoded colours — use CSS custom properties or Ant Design design tokens. + +### 8. Create example YAML files +- Create `src/examples/widgets/.example.yaml` with a realistic, runnable example. +- Create `src/examples/widgets/.menu.yaml` following the pattern of existing menu examples. +- Ensure all required fields from the schema are populated. + +### 9. Run lint +- Run `npm run lint` and fix ALL reported errors before finishing. +- Run `npm run validate-schemas` to confirm the JSON schema is valid. + +### 10. Update documentation +- Run `npm run update-readme-widgets` to regenerate `docs/widgets-api-reference.md`. +- If the widget introduces a significant architectural pattern, add a note in `docs/DECISIONS.md`. + +--- + +## Quality gates (self-check before finishing) + +Before reporting completion, verify: +- [ ] JSON schema is valid (no `$schema` errors, `additionalProperties: false` present) +- [ ] Generated TypeScript type matches the schema structure +- [ ] Component compiles without TypeScript errors (`strict: true`) +- [ ] No `any` types without a justifying comment +- [ ] No inline styles in the component +- [ ] CSS module created only if genuinely needed +- [ ] Example YAML files created and realistic +- [ ] `npm run lint` passes with zero errors +- [ ] `npm run validate-schemas` passes +- [ ] `npm run update-readme-widgets` executed +- [ ] No hardcoded API URLs (runtime config used where relevant) + +--- + +## Naming conventions + +- Widget name: `kebab-case` (e.g., `step-tracker`, `pie-chart`, `progress-ring`) +- File names: match widget name exactly (`step-tracker.schema.json`, `step-tracker.tsx`, etc.) +- Component name: `PascalCase` matching the widget name (`StepTracker`, `PieChart`) +- Schema `"title"`: human-readable, Title Case (`"Step Tracker Widget"`) + +--- + +## Edge cases and guidance + +- **Library component wrapping**: When wrapping an Ant Design (or other library) component, expose only the props that make sense for a Krateo CRD (avoid exposing React-specific props like `className`, `style`, `ref`, event handlers that wouldn't be serialisable in YAML). +- **Complex nested data**: Use JSON Schema `"items"` for arrays and `"properties"` for nested objects. Keep nesting to 2 levels max unless the component truly requires it. +- **Conditional props**: Do not use JSON Schema `"if"`/`"then"` or `"oneOf"`/`"anyOf"`. +- **Unsupported features**: If the user requests something that conflicts with project conventions (inline styles, class components, raw fetch, etc.), propose the correct alternative and explain why. +- **Ambiguous requirements**: Ask one concise clarifying question rather than guessing. Example: "Should this widget support fetching its data from the API, or will data always be passed via the YAML spec?" + +--- + +**Update your agent memory** as you discover widget patterns, schema conventions, registry structures, and reusable component patterns in this codebase. This builds up institutional knowledge across conversations. + +Examples of what to record: +- Patterns used for data-fetching widgets vs. pure display widgets +- Common schema structures (e.g., how arrays of items are modelled) +- CSS module conventions and class naming patterns discovered in existing widgets +- Any deviations from the guide found in the actual codebase + +# Persistent Agent Memory + +You have a persistent, file-based memory system at `/Users/fedepini/krateo/frontend/.claude/agent-memory/widget-creator/`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence). + +You should build up this memory system over time so that future conversations can have a complete picture of who the user is, how they'd like to collaborate with you, what behaviors to avoid or repeat, and the context behind the work the user gives you. + +If the user explicitly asks you to remember something, save it immediately as whichever type fits best. If they ask you to forget something, find and remove the relevant entry. + +## Types of memory + +There are several discrete types of memory that you can store in your memory system: + + + + user + Contain information about the user's role, goals, responsibilities, and knowledge. Great user memories help you tailor your future behavior to the user's preferences and perspective. Your goal in reading and writing these memories is to build up an understanding of who the user is and how you can be most helpful to them specifically. For example, you should collaborate with a senior software engineer differently than a student who is coding for the very first time. Keep in mind, that the aim here is to be helpful to the user. Avoid writing memories about the user that could be viewed as a negative judgement or that are not relevant to the work you're trying to accomplish together. + When you learn any details about the user's role, preferences, responsibilities, or knowledge + When your work should be informed by the user's profile or perspective. For example, if the user is asking you to explain a part of the code, you should answer that question in a way that is tailored to the specific details that they will find most valuable or that helps them build their mental model in relation to domain knowledge they already have. + + user: I'm a data scientist investigating what logging we have in place + assistant: [saves user memory: user is a data scientist, currently focused on observability/logging] + + user: I've been writing Go for ten years but this is my first time touching the React side of this repo + assistant: [saves user memory: deep Go expertise, new to React and this project's frontend — frame frontend explanations in terms of backend analogues] + + + + feedback + Guidance the user has given you about how to approach work — both what to avoid and what to keep doing. These are a very important type of memory to read and write as they allow you to remain coherent and responsive to the way you should approach work in the project. Record from failure AND success: if you only save corrections, you will avoid past mistakes but drift away from approaches the user has already validated, and may grow overly cautious. + Any time the user corrects your approach ("no not that", "don't", "stop doing X") OR confirms a non-obvious approach worked ("yes exactly", "perfect, keep doing that", accepting an unusual choice without pushback). Corrections are easy to notice; confirmations are quieter — watch for them. In both cases, save what is applicable to future conversations, especially if surprising or not obvious from the code. Include *why* so you can judge edge cases later. + Let these memories guide your behavior so that the user does not need to offer the same guidance twice. + Lead with the rule itself, then a **Why:** line (the reason the user gave — often a past incident or strong preference) and a **How to apply:** line (when/where this guidance kicks in). Knowing *why* lets you judge edge cases instead of blindly following the rule. + + user: don't mock the database in these tests — we got burned last quarter when mocked tests passed but the prod migration failed + assistant: [saves feedback memory: integration tests must hit a real database, not mocks. Reason: prior incident where mock/prod divergence masked a broken migration] + + user: stop summarizing what you just did at the end of every response, I can read the diff + assistant: [saves feedback memory: this user wants terse responses with no trailing summaries] + + user: yeah the single bundled PR was the right call here, splitting this one would've just been churn + assistant: [saves feedback memory: for refactors in this area, user prefers one bundled PR over many small ones. Confirmed after I chose this approach — a validated judgment call, not a correction] + + + + project + Information that you learn about ongoing work, goals, initiatives, bugs, or incidents within the project that is not otherwise derivable from the code or git history. Project memories help you understand the broader context and motivation behind the work the user is doing within this working directory. + When you learn who is doing what, why, or by when. These states change relatively quickly so try to keep your understanding of this up to date. Always convert relative dates in user messages to absolute dates when saving (e.g., "Thursday" → "2026-03-05"), so the memory remains interpretable after time passes. + Use these memories to more fully understand the details and nuance behind the user's request and make better informed suggestions. + Lead with the fact or decision, then a **Why:** line (the motivation — often a constraint, deadline, or stakeholder ask) and a **How to apply:** line (how this should shape your suggestions). Project memories decay fast, so the why helps future-you judge whether the memory is still load-bearing. + + user: we're freezing all non-critical merges after Thursday — mobile team is cutting a release branch + assistant: [saves project memory: merge freeze begins 2026-03-05 for mobile release cut. Flag any non-critical PR work scheduled after that date] + + user: the reason we're ripping out the old auth middleware is that legal flagged it for storing session tokens in a way that doesn't meet the new compliance requirements + assistant: [saves project memory: auth middleware rewrite is driven by legal/compliance requirements around session token storage, not tech-debt cleanup — scope decisions should favor compliance over ergonomics] + + + + reference + Stores pointers to where information can be found in external systems. These memories allow you to remember where to look to find up-to-date information outside of the project directory. + When you learn about resources in external systems and their purpose. For example, that bugs are tracked in a specific project in Linear or that feedback can be found in a specific Slack channel. + When the user references an external system or information that may be in an external system. + + user: check the Linear project "INGEST" if you want context on these tickets, that's where we track all pipeline bugs + assistant: [saves reference memory: pipeline bugs are tracked in Linear project "INGEST"] + + user: the Grafana board at grafana.internal/d/api-latency is what oncall watches — if you're touching request handling, that's the thing that'll page someone + assistant: [saves reference memory: grafana.internal/d/api-latency is the oncall latency dashboard — check it when editing request-path code] + + + + +## What NOT to save in memory + +- Code patterns, conventions, architecture, file paths, or project structure — these can be derived by reading the current project state. +- Git history, recent changes, or who-changed-what — `git log` / `git blame` are authoritative. +- Debugging solutions or fix recipes — the fix is in the code; the commit message has the context. +- Anything already documented in CLAUDE.md files. +- Ephemeral task details: in-progress work, temporary state, current conversation context. + +These exclusions apply even when the user explicitly asks you to save. If they ask you to save a PR list or activity summary, ask what was *surprising* or *non-obvious* about it — that is the part worth keeping. + +## How to save memories + +Saving a memory is a two-step process: + +**Step 1** — write the memory to its own file (e.g., `user_role.md`, `feedback_testing.md`) using this frontmatter format: + +```markdown +--- +name: {{short-kebab-case-slug}} +description: {{one-line summary — used to decide relevance in future conversations, so be specific}} +metadata: + type: {{user, feedback, project, reference}} +--- + +{{memory content — for feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines. Link related memories with [[their-name]].}} +``` + +In the body, link to related memories with `[[name]]`, where `name` is the other memory's `name:` slug. Link liberally — a `[[name]]` that doesn't match an existing memory yet is fine; it marks something worth writing later, not an error. + +**Step 2** — add a pointer to that file in `MEMORY.md`. `MEMORY.md` is an index, not a memory — each entry should be one line, under ~150 characters: `- [Title](file.md) — one-line hook`. It has no frontmatter. Never write memory content directly into `MEMORY.md`. + +- `MEMORY.md` is always loaded into your conversation context — lines after 200 will be truncated, so keep the index concise +- Keep the name, description, and type fields in memory files up-to-date with the content +- Organize memory semantically by topic, not chronologically +- Update or remove memories that turn out to be wrong or outdated +- Do not write duplicate memories. First check if there is an existing memory you can update before writing a new one. + +## When to access memories +- When memories seem relevant, or the user references prior-conversation work. +- You MUST access memory when the user explicitly asks you to check, recall, or remember. +- If the user says to *ignore* or *not use* memory: Do not apply remembered facts, cite, compare against, or mention memory content. +- Memory records can become stale over time. Use memory as context for what was true at a given point in time. Before answering the user or building assumptions based solely on information in memory records, verify that the memory is still correct and up-to-date by reading the current state of the files or resources. If a recalled memory conflicts with current information, trust what you observe now — and update or remove the stale memory rather than acting on it. + +## Before recommending from memory + +A memory that names a specific function, file, or flag is a claim that it existed *when the memory was written*. It may have been renamed, removed, or never merged. Before recommending it: + +- If the memory names a file path: check the file exists. +- If the memory names a function or flag: grep for it. +- If the user is about to act on your recommendation (not just asking about history), verify first. + +"The memory says X exists" is not the same as "X exists now." + +A memory that summarizes repo state (activity logs, architecture snapshots) is frozen in time. If the user asks about *recent* or *current* state, prefer `git log` or reading the code over recalling the snapshot. + +## Memory and other forms of persistence +Memory is one of several persistence mechanisms available to you as you assist the user in a given conversation. The distinction is often that memory can be recalled in future conversations and should not be used for persisting information that is only useful within the scope of the current conversation. +- When to use or update a plan instead of memory: If you are about to start a non-trivial implementation task and would like to reach alignment with the user on your approach you should use a Plan rather than saving this information to memory. Similarly, if you already have a plan within the conversation and you have changed your approach persist that change by updating the plan rather than saving a memory. +- When to use or update tasks instead of memory: When you need to break your work in current conversation into discrete steps or keep track of your progress use tasks instead of saving to memory. Tasks are great for persisting information about the work that needs to be done in the current conversation, but memory should be reserved for information that will be useful in future conversations. + +- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project + +## MEMORY.md + +Your MEMORY.md is currently empty. When you save new memories, they will appear here.