Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md - Giselle Development Guide

## Development Philosophy

### Core Principle: **Less is more**
Keep every implementation as small and obvious as possible.

### Guidelines
- **Simplicity first** – Prefer the simplest data structures and APIs that work
- **Avoid needless abstractions** – Refactor only when duplication hurts
- **Remove dead code early** – `pnpm tidy` scans for unused files/deps and lets you delete them in one command
- **Minimize dependencies** – Before adding a dependency, ask "Can we do this with what we already have?"
- **Consistency wins** – Follow existing naming and file-layout patterns; if you must diverge, document why
- **Explicit over implicit** – Favor clear, descriptive names and type annotations over clever tricks
- **Fail fast** – Validate inputs, throw early, and surface actionable errors
- **Let the code speak** – If you need a multi-paragraph comment, refactor until intent is obvious

## Project Overview

Giselle is built to design and run AI workflows beyond prompt chains. Not a chat. Not a chain. A system you can run.

### Key Features:

- Visual editor
- Instant execution
- No infra headaches
- Open source — focused on local use and self-hosting

## Architecture

### Monorepo Structure

Giselle uses a **Turborepo monorepo** with pnpm workspaces, organized into four main directories:

```
/workspace
├── apps/                    # Deployable applications
│   ├── studio.giselles.ai/  # Giselle Studio application
│   └── ui.giselles.ai/      # UI component showcase
├── packages/                # Published SDK packages (@giselles-ai/*)
├── internal-packages/       # Internal shared packages (@giselle-internal/*)
└── tools/                   # Development utilities
```

### Package Layers

**SDK Packages (`packages/@giselles-ai/*`):**
- `protocol` — Core domain types and schemas (Workspace, Node, Task, Generation)
- `giselle` — Engine implementation (tasks, generations, triggers, integrations)
- `react` — React hooks and components for client integration
- `nextjs` — Next.js integration with route handlers
- `language-model` — Language model abstractions and cost calculations
- `language-model-registry` — Provider-specific model implementations
- `rag` — RAG pipeline (chunking, embedding, querying)
- `github-tool` — GitHub integration utilities

**Internal Packages (`internal-packages/@giselle-internal/*`):**
- `workflow-designer-ui` — Visual workflow editor (React Flow-based)
- `ui` — Shared UI components (Radix-based)

### Technology Stack

| Layer | Technology |
|-------|------------|
| Runtime | Node.js 22+ |
| Package Manager | pnpm 10+ |
| Build System | Turborepo |
| Web Framework | Next.js 16 (App Router) |
| UI Library | React 19 |
| Styling | Tailwind CSS 4 |
| State Management | Zustand (editor), SWR (data fetching) |
| Validation | Zod v4 |
| Database | PostgreSQL with Drizzle ORM |
| Vector Store | pgvector |
| Formatting/Linting | Biome |
| Testing | Vitest |
| AI SDK | Vercel AI SDK |

### Data Flow

```
Workspace (JSON) → Protocol Types → Giselle Engine → Task Execution → Generation Output
                                          ↓
                          Language Model Registry → AI Provider APIs
```

### Key Domain Concepts

- **Workspace** — A visual workflow containing nodes and connections
- **Node** — Either an OperationNode (actions, text generation, triggers) or VariableNode (text, files, vector stores)
- **Task** — An executable instance of a workflow with sequences of generations
- **Generation** — A single step execution (created → queued → running → completed/failed)
- **App** — A published workflow entry point with parameters

## Development Workflow

TBD
### Initial Setup

```sh
pnpm install        # Install all dependencies
pnpm build-sdk      # Build SDK packages (required before running apps)
```

### Development Commands

```sh
# Development
pnpm dev:studio.giselles.ai  # Start Giselle Studio

# Build
pnpm build-sdk               # Build SDK packages
pnpm -F studio.giselles.ai build  # Build Giselle Studio

# Quality Checks
pnpm format                  # Format code with Biome
pnpm check-types             # Type-check all packages
pnpm test                    # Run all tests
pnpm tidy                    # Find unused files/dependencies
pnpm tidy --fix              # Remove unused files/dependencies
```

### After Every Code Change

Run these commands in order:
1. `pnpm format` — Format code
2. `pnpm build-sdk` — Rebuild SDK packages
3. `pnpm check-types` — Verify types
4. `pnpm tidy` — Check for unused code
5. `pnpm test` — Run tests

### API addition rule (Giselle ↔ HTTP)

When adding a new **public API** to `packages/giselle/src/giselle.ts`, also add the corresponding routing entry to `packages/http/src/router.ts` (typically `jsonRoutes.<name>` using `giselle.<name>.inputSchema`) so the API is reachable through the HTTP layer (e.g., via `NextGiselle`).

### Testing

```sh
pnpm test                           # Run all tests
pnpm -F @giselles-ai/giselle test   # Run tests for a specific package
cd packages/giselle && vitest       # Run tests in watch mode
vitest run src/tasks/run-task.test.ts  # Run a specific test file
```

Test files follow the `*.test.ts` naming pattern and use Vitest.

### Pull Request Guidelines

- Create PRs in **meaningful minimum units** — even 1 commit or ~20 lines is fine
- Feature flags protect unreleased features, so submit PRs for any meaningful work
- **~500 lines**: Consider wrapping up for a PR
- **1000 lines**: Maximum threshold — avoid exceeding this

## Key Conventions

### Naming

TBD
**File Names: kebab-case**
```
✅ user-profile.ts
✅ api-client.tsx
✅ text-generation.ts
❌ UserProfile.ts
❌ apiClient.tsx
```

**Components: PascalCase**
```
✅ UserProfile
✅ TextGenerationNode
❌ userProfile
```

**Variables and Functions: camelCase**
```
✅ userEmail
✅ calculateTotalPrice()
✅ validateUserInput()
❌ user_email
```

**Booleans: Prefix with `is`, `has`, `can`, `should`**
```
✅ isEnabled, hasPermission, canEdit, shouldRetry
✅ isCompletedGeneration(), hasActiveSubscription()
❌ enabled, permission, completed
```

**ID Types: Prefixed strings with branded types**
```typescript
// packages/protocol/src/node/base.ts
export const NodeId = createIdGenerator("nd");     // "nd_xxx"
export const InputId = createIdGenerator("inp");   // "inp_xxx"
export const OutputId = createIdGenerator("otp");  // "otp_xxx"
```

### Code Style

TBD
**Formatting (Biome)**
- Tab indentation
- Double quotes for strings
- Organized imports (auto-sorted)

**TypeScript**
- Prefer explicit types over `any`
- Use Zod schemas for runtime validation and type inference:
  ```typescript
  export const Workspace = z.object({
    id: WorkspaceId.schema,
    nodes: z.array(NodeLike),
    connections: z.array(Connection),
  });
  export type Workspace = z.infer<typeof Workspace>;
  ```
- Use discriminated unions for variant types:
  ```typescript
  export const Node = z.discriminatedUnion("type", [
    OperationNode,
    VariableNode,
  ]);
  ```

**React**
- Functional components with hooks
- Zustand for complex state (editor store)
- SWR for server data fetching
- Selective subscriptions to minimize re-renders:
  ```typescript
  // Good: Subscribe only to needed data
  const node = useEditorStore((s) => s.nodesById[nodeId]);
  
  // Bad: Subscribe to entire state
  const state = useEditorStore((s) => s);
  ```

**Async/Await**
- Prefer async/await over raw promises
- Use try/catch for error handling

### Error Handling

TBD
**Custom Error Classes with Symbol Markers**

For cross-package error identification, use Symbol-based instance checking:

```typescript
const marker = "giselle.react.error.APICallError";
const symbol = Symbol.for(marker);

export class APICallError extends ReactError {
  private readonly [symbol] = true;
  
  static isInstance(error: unknown): error is APICallError {
    return ReactError.hasMarker(error, marker);
  }
}
```

**Validation Errors**

Handle Zod validation errors at API boundaries:
```typescript
try {
  return await jsonRoutes[routerPath](giselle)({ input });
} catch (e) {
  if (e instanceof ZodError) {
    return new Response("Invalid request body", { status: 400 });
  }
  return new Response("Internal Server Error", { status: 500 });
}
```

**Exhaustive Type Checking**

Use `never` type for exhaustive switch statements:
```typescript
switch (generation.context.operationNode.content.type) {
  case "action":
    // handle action
    break;
  case "textGeneration":
    // handle text generation
    break;
  default: {
    const _exhaustiveCheck: never = generation.context.operationNode.content.type;
    throw new Error(`Unhandled type: ${_exhaustiveCheck}`);
  }
}
```

**Fail Fast**

Validate inputs early and throw with actionable messages:
```typescript
if (!generation) {
  throw new Error(`Generation(id: ${generationId}) is not found`);
}

if (!config.vectorStoreQueryService) {
  throw new Error("No vector store query service provided");
}
```

### Feature Flags

Feature flags protect unreleased features, allowing safe merges to main and production deploys.

**Step 1: Define the flag in `apps/studio.giselles.ai/flags.ts`**

```typescript
export const myNewFeatureFlag = flag<boolean>({
  key: "my-new-feature",
  decide() {
    return process.env.MY_NEW_FEATURE_FLAG === "true";
  },
  description: "Enable my new feature",
  options: [
    { value: false, label: "disable" },
    { value: true, label: "Enable" },
  ],
  defaultValue: false,
});
```

**Step 2: Use on server (Next.js server components, data loaders)**

```typescript
// apps/studio.giselles.ai/app/workspaces/[workspaceId]/data-loader.ts
const myNewFeature = await myNewFeatureFlag();
return {
  // ...
  featureFlags: {
    // ...existing flags
    myNewFeature,
  },
};
```

**Step 3: Expose to React components**

Add the flag to the `FeatureFlagContextValue` interface:

```typescript
// packages/react/src/feature-flags/context.ts
export interface FeatureFlagContextValue {
  // ...existing flags
  myNewFeature: boolean;
}
```

Add to `WorkspaceProvider` defaults:

```typescript
// packages/react/src/workspace/provider.tsx
<FeatureFlagContext
  value={{
    // ...existing flags
    myNewFeature: featureFlag?.myNewFeature ?? false,
  }}
>
```

**Step 4: Use in React components**

```typescript
import { useFeatureFlag } from "@giselles-ai/react";

function MyComponent() {
  const { myNewFeature } = useFeatureFlag();
  
  if (!myNewFeature) {
    return null; // or fallback UI
  }
  
  return <NewFeatureUI />;
}
```

**Local development**: Set the environment variable (e.g., `MY_NEW_FEATURE_FLAG=true`) in `.env.local`.

**Production**: Set the same environment variable on the server. Only `"true"` enables the flag. Restart the server after changing configuration; rebuild when a flag is evaluated at build time.

<!-- opensrc:start -->

## Source Code Reference

Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details.

See `opensrc/sources.json` for the list of available packages and their versions.

Use this source code when you need to understand how a package works internally, not just its types/interface.

### Fetching Additional Source Code

To fetch source code for a package or repository you need to understand, run:

```bash
npx opensrc <package>           # npm package (e.g., npx opensrc zod)
npx opensrc pypi:<package>      # Python package (e.g., npx opensrc pypi:requests)
npx opensrc crates:<package>    # Rust crate (e.g., npx opensrc crates:serde)
npx opensrc <owner>/<repo>      # GitHub repo (e.g., npx opensrc vercel/ai)
```

<!-- opensrc:end -->