diff --git a/packages/browseros-agent/.agents/skills/ai-sdk/SKILL.md b/packages/browseros-agent/.agents/skills/ai-sdk/SKILL.md new file mode 100644 index 000000000..f4ac34653 --- /dev/null +++ b/packages/browseros-agent/.agents/skills/ai-sdk/SKILL.md @@ -0,0 +1,78 @@ +--- +name: ai-sdk +description: 'Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: "AI SDK", "Vercel AI SDK", "generateText", "streamText", "add AI to my app", "build an agent", "tool calling", "structured output", "useChat".' +--- + +## Prerequisites + +Before searching docs, check if `node_modules/ai/docs/` exists. If not, install **only** the `ai` package using the project's package manager (e.g., `pnpm add ai`). + +Do not install other packages at this stage. Provider packages (e.g., `@ai-sdk/openai`) and client packages (e.g., `@ai-sdk/react`) should be installed later when needed based on user requirements. + +## Critical: Do Not Trust Internal Knowledge + +Everything you know about the AI SDK is outdated or wrong. Your training data contains obsolete APIs, deprecated patterns, and incorrect usage. + +**When working with the AI SDK:** + +1. Ensure `ai` package is installed (see Prerequisites) +2. Search `node_modules/ai/docs/` and `node_modules/ai/src/` for current APIs +3. If not found locally, search ai-sdk.dev documentation (instructions below) +4. Never rely on memory - always verify against source code or docs +5. **`useChat` has changed significantly** - check [Common Errors](references/common-errors.md) before writing client code +6. When deciding which model and provider to use (e.g. OpenAI, Anthropic, Gemini), use the Vercel AI Gateway provider unless the user specifies otherwise. See [AI Gateway Reference](references/ai-gateway.md) for usage details. +7. **Always fetch current model IDs** - Never use model IDs from memory. Before writing code that uses a model, run `curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith("provider/")) | .id] | reverse | .[]'` (replacing `provider` with the relevant provider like `anthropic`, `openai`, or `google`) to get the full list with newest models first. Use the model with the highest version number (e.g., `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`). +8. Run typecheck after changes to ensure code is correct +9. **Be minimal** - Only specify options that differ from defaults. When unsure of defaults, check docs or source rather than guessing or over-specifying. + +If you cannot find documentation to support your answer, state that explicitly. + +## Finding Documentation + +### ai@6.0.34+ + +Search bundled docs and source in `node_modules/ai/`: + +- **Docs**: `grep "query" node_modules/ai/docs/` +- **Source**: `grep "query" node_modules/ai/src/` + +Provider packages include docs at `node_modules/@ai-sdk//docs/`. + +### Earlier versions + +1. Search: `https://ai-sdk.dev/api/search-docs?q=your_query` +2. Fetch `.md` URLs from results (e.g., `https://ai-sdk.dev/docs/agents/building-agents.md`) + +## When Typecheck Fails + +**Before searching source code**, grep [Common Errors](references/common-errors.md) for the failing property or function name. Many type errors are caused by deprecated APIs documented there. + +If not found in common-errors.md: + +1. Search `node_modules/ai/src/` and `node_modules/ai/docs/` +2. Search ai-sdk.dev (for earlier versions or if not found locally) + +## Building and Consuming Agents + +### Creating Agents + +Always use the `ToolLoopAgent` pattern. Search `node_modules/ai/docs/` for current agent creation APIs. + +**File conventions**: See [type-safe-agents.md](references/type-safe-agents.md) for where to save agents and tools. + +**Type Safety**: When consuming agents with `useChat`, always use `InferAgentUIMessage` for type-safe tool results. See [reference](references/type-safe-agents.md). + +### Consuming Agents (Framework-Specific) + +Before implementing agent consumption: + +1. Check `package.json` to detect the project's framework/stack +2. Search documentation for the framework's quickstart guide +3. Follow the framework-specific patterns for streaming, API routes, and client integration + +## References + +- [Common Errors](references/common-errors.md) - Renamed parameters reference (parameters → inputSchema, etc.) +- [AI Gateway](references/ai-gateway.md) - Gateway setup and usage +- [Type-Safe Agents with useChat](references/type-safe-agents.md) - End-to-end type safety with InferAgentUIMessage +- [DevTools](references/devtools.md) - Set up local debugging and observability (development only) diff --git a/packages/browseros-agent/.agents/skills/ai-sdk/references/ai-gateway.md b/packages/browseros-agent/.agents/skills/ai-sdk/references/ai-gateway.md new file mode 100644 index 000000000..8bb2d66ff --- /dev/null +++ b/packages/browseros-agent/.agents/skills/ai-sdk/references/ai-gateway.md @@ -0,0 +1,66 @@ +--- +title: Vercel AI Gateway +description: Reference for using Vercel AI Gateway with the AI SDK. +--- + +# Vercel AI Gateway + +The Vercel AI Gateway is the fastest way to get started with the AI SDK. It provides access to models from OpenAI, Anthropic, Google, and other providers through a single API. + +## Authentication + +Authenticate with OIDC (for Vercel deployments) or an [AI Gateway API key](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys): + +```env filename=".env.local" +AI_GATEWAY_API_KEY=your_api_key_here +``` + +## Usage + +The AI Gateway is the default global provider, so you can access models using a simple string: + +```ts +import { generateText } from 'ai'; + +const { text } = await generateText({ + model: 'anthropic/claude-sonnet-4.5', + prompt: 'What is love?', +}); +``` + +You can also explicitly import and use the gateway provider: + +```ts +// Option 1: Import from 'ai' package (included by default) +import { gateway } from 'ai'; +model: gateway('anthropic/claude-sonnet-4.5'); + +// Option 2: Install and import from '@ai-sdk/gateway' package +import { gateway } from '@ai-sdk/gateway'; +model: gateway('anthropic/claude-sonnet-4.5'); +``` + +## Find Available Models + +**Important**: Always fetch the current model list before writing code. Never use model IDs from memory - they may be outdated. + +List all available models through the gateway API: + +```bash +curl https://ai-gateway.vercel.sh/v1/models +``` + +Filter by provider using `jq`. **Do not truncate with `head`** - always fetch the full list to find the latest models: + +```bash +# Anthropic models +curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith("anthropic/")) | .id] | reverse | .[]' + +# OpenAI models +curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith("openai/")) | .id] | reverse | .[]' + +# Google models +curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith("google/")) | .id] | reverse | .[]' +``` + +When multiple versions of a model exist, use the one with the highest version number (e.g., prefer `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`). diff --git a/packages/browseros-agent/.agents/skills/ai-sdk/references/common-errors.md b/packages/browseros-agent/.agents/skills/ai-sdk/references/common-errors.md new file mode 100644 index 000000000..529521ebe --- /dev/null +++ b/packages/browseros-agent/.agents/skills/ai-sdk/references/common-errors.md @@ -0,0 +1,443 @@ +--- +title: Common Errors +description: Reference for common AI SDK errors and how to resolve them. +--- + +# Common Errors + +## `maxTokens` → `maxOutputTokens` + +```typescript +// ❌ Incorrect +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + maxTokens: 512, // deprecated: use `maxOutputTokens` instead + prompt: 'Write a short story', +}); + +// ✅ Correct +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + maxOutputTokens: 512, + prompt: 'Write a short story', +}); +``` + +## `maxSteps` → `stopWhen: stepCountIs(n)` + +```typescript +// ❌ Incorrect +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + tools: { weather }, + maxSteps: 5, // deprecated: use `stopWhen: stepCountIs(n)` instead + prompt: 'What is the weather in NYC?', +}); + +// ✅ Correct +import { generateText, stepCountIs } from 'ai'; + +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + tools: { weather }, + stopWhen: stepCountIs(5), + prompt: 'What is the weather in NYC?', +}); +``` + +## `parameters` → `inputSchema` (in tool definition) + +```typescript +// ❌ Incorrect +const weatherTool = tool({ + description: 'Get weather for a location', + parameters: z.object({ + // deprecated: use `inputSchema` instead + location: z.string(), + }), + execute: async ({ location }) => ({ location, temp: 72 }), +}); + +// ✅ Correct +const weatherTool = tool({ + description: 'Get weather for a location', + inputSchema: z.object({ + location: z.string(), + }), + execute: async ({ location }) => ({ location, temp: 72 }), +}); +``` + +## `generateObject` → `generateText` with `output` + +`generateObject` is deprecated. Use `generateText` with the `output` option instead. + +```typescript +// ❌ Deprecated +import { generateObject } from 'ai'; // deprecated: use `generateText` with `output` instead + +const result = await generateObject({ + // deprecated function + model: 'anthropic/claude-opus-4.5', + schema: z.object({ + // deprecated: use `Output.object({ schema })` instead + recipe: z.object({ + name: z.string(), + ingredients: z.array(z.string()), + }), + }), + prompt: 'Generate a recipe for chocolate cake', +}); + +// ✅ Correct +import { generateText, Output } from 'ai'; + +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + output: Output.object({ + schema: z.object({ + recipe: z.object({ + name: z.string(), + ingredients: z.array(z.string()), + }), + }), + }), + prompt: 'Generate a recipe for chocolate cake', +}); + +console.log(result.output); // typed object +``` + +## Manual JSON parsing → `generateText` with `output` + +```typescript +// ❌ Incorrect +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + prompt: `Extract the user info as JSON: { "name": string, "age": number } + + Input: John is 25 years old`, +}); +const parsed = JSON.parse(result.text); + +// ✅ Correct +import { generateText, Output } from 'ai'; + +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + output: Output.object({ + schema: z.object({ + name: z.string(), + age: z.number(), + }), + }), + prompt: 'Extract the user info: John is 25 years old', +}); + +console.log(result.output); // { name: 'John', age: 25 } +``` + +## Other `output` options + +```typescript +// Output.array - for generating arrays of items +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + output: Output.array({ + element: z.object({ + city: z.string(), + country: z.string(), + }), + }), + prompt: 'List 5 capital cities', +}); + +// Output.choice - for selecting from predefined options +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + output: Output.choice({ + options: ['positive', 'negative', 'neutral'] as const, + }), + prompt: 'Classify the sentiment: I love this product!', +}); + +// Output.json - for untyped JSON output +const result = await generateText({ + model: 'anthropic/claude-opus-4.5', + output: Output.json(), + prompt: 'Return some JSON data', +}); +``` + +## `toDataStreamResponse` → `toUIMessageStreamResponse` + +When using `useChat` on the frontend, use `toUIMessageStreamResponse()` instead of `toDataStreamResponse()`. The UI message stream format is designed to work with the chat UI components and handles message state correctly. + +```typescript +// ❌ Incorrect (when using useChat) +const result = streamText({ + // config +}); + +return result.toDataStreamResponse(); // deprecated for useChat: use toUIMessageStreamResponse + +// ✅ Correct +const result = streamText({ + // config +}); + +return result.toUIMessageStreamResponse(); +``` + +## Removed managed input state in `useChat` + +The `useChat` hook no longer manages input state internally. You must now manage input state manually. + +```tsx +// ❌ Deprecated +import { useChat } from '@ai-sdk/react'; + +export default function Page() { + const { + input, // deprecated: manage input state manually with useState + handleInputChange, // deprecated: use custom onChange handler + handleSubmit, // deprecated: use sendMessage() instead + } = useChat({ + api: '/api/chat', // deprecated: use `transport: new DefaultChatTransport({ api })` instead + }); + + return ( +
+ + +
+ ); +} + +// ✅ Correct +import { useChat } from '@ai-sdk/react'; +import { DefaultChatTransport } from 'ai'; +import { useState } from 'react'; + +export default function Page() { + const [input, setInput] = useState(''); + const { sendMessage } = useChat({ + transport: new DefaultChatTransport({ api: '/api/chat' }), + }); + + const handleSubmit = e => { + e.preventDefault(); + sendMessage({ text: input }); + setInput(''); + }; + + return ( +
+ setInput(e.target.value)} /> + +
+ ); +} +``` + +## `tool-invocation` → `tool-{toolName}` (typed tool parts) + +When rendering messages with `useChat`, use the typed tool part names (`tool-{toolName}`) instead of the generic `tool-invocation` type. This provides better type safety and access to tool-specific input/output types. + +> For end-to-end type-safety, see [Type-Safe Agents](type-safe-agents.md). + +Typed tool parts also use different property names: + +- `part.args` → `part.input` +- `part.result` → `part.output` + +```tsx +// ❌ Incorrect - using generic tool-invocation +{ + message.parts.map((part, i) => { + switch (part.type) { + case 'text': + return
{part.text}
; + case 'tool-invocation': // deprecated: use typed tool parts instead + return ( +
+            {JSON.stringify(part.toolInvocation, null, 2)}
+          
+ ); + } + }); +} + +// ✅ Correct - using typed tool parts (recommended) +{ + message.parts.map(part => { + switch (part.type) { + case 'text': + return part.text; + case 'tool-askForConfirmation': + // handle askForConfirmation tool + break; + case 'tool-getWeatherInformation': + // handle getWeatherInformation tool + break; + } + }); +} + +// ✅ Alternative - using isToolUIPart as a catch-all +import { isToolUIPart } from 'ai'; + +{ + message.parts.map(part => { + if (part.type === 'text') { + return part.text; + } + if (isToolUIPart(part)) { + // handle any tool part generically + return ( +
+ {part.toolName}: {part.state} +
+ ); + } + }); +} +``` + +## `useChat` state-dependent property access + +Tool part properties are only available in certain states. TypeScript will error if you access them without checking state first. + +```tsx +// ❌ Incorrect - input may be undefined during streaming +// TS18048: 'part.input' is possibly 'undefined' +if (part.type === 'tool-getWeather') { + const location = part.input.location; +} + +// ✅ Correct - check for input-available or output-available +if ( + part.type === 'tool-getWeather' && + (part.state === 'input-available' || part.state === 'output-available') +) { + const location = part.input.location; +} + +// ❌ Incorrect - output is only available after execution +// TS18048: 'part.output' is possibly 'undefined' +if (part.type === 'tool-getWeather') { + const weather = part.output; +} + +// ✅ Correct - check for output-available +if (part.type === 'tool-getWeather' && part.state === 'output-available') { + const location = part.input.location; + const weather = part.output; +} +``` + +## `part.toolInvocation.args` → `part.input` + +```tsx +// ❌ Incorrect +if (part.type === 'tool-invocation') { + // deprecated: use `part.input` on typed tool parts instead + const location = part.toolInvocation.args.location; +} + +// ✅ Correct +if ( + part.type === 'tool-getWeather' && + (part.state === 'input-available' || part.state === 'output-available') +) { + const location = part.input.location; +} +``` + +## `part.toolInvocation.result` → `part.output` + +```tsx +// ❌ Incorrect +if (part.type === 'tool-invocation') { + // deprecated: use `part.output` on typed tool parts instead + const weather = part.toolInvocation.result; +} + +// ✅ Correct +if (part.type === 'tool-getWeather' && part.state === 'output-available') { + const weather = part.output; +} +``` + +## `part.toolInvocation.toolCallId` → `part.toolCallId` + +```tsx +// ❌ Incorrect +if (part.type === 'tool-invocation') { + // deprecated: use `part.toolCallId` on typed tool parts instead + const id = part.toolInvocation.toolCallId; +} + +// ✅ Correct +if (part.type === 'tool-getWeather') { + const id = part.toolCallId; +} +``` + +## Tool invocation states renamed + +```tsx +// ❌ Incorrect +switch (part.toolInvocation.state) { + case 'partial-call': // deprecated: use `input-streaming` instead + return
Loading...
; + case 'call': // deprecated: use `input-available` instead + return
Executing...
; + case 'result': // deprecated: use `output-available` instead + return
Done
; +} + +// ✅ Correct +switch (part.state) { + case 'input-streaming': + return
Loading...
; + case 'input-available': + return
Executing...
; + case 'output-available': + return
Done
; +} +``` + +## `addToolResult` → `addToolOutput` + +```tsx +// ❌ Incorrect +addToolResult({ + // deprecated: use `addToolOutput` instead + toolCallId: part.toolInvocation.toolCallId, + result: 'Yes, confirmed.', // deprecated: use `output` instead +}); + +// ✅ Correct +addToolOutput({ + tool: 'askForConfirmation', + toolCallId: part.toolCallId, + output: 'Yes, confirmed.', +}); +``` + +## `messages` → `uiMessages` in `createAgentUIStreamResponse` + +```typescript +// ❌ Incorrect +return createAgentUIStreamResponse({ + agent: myAgent, + messages, // incorrect: use `uiMessages` instead +}); + +// ✅ Correct +return createAgentUIStreamResponse({ + agent: myAgent, + uiMessages: messages, +}); +``` diff --git a/packages/browseros-agent/.agents/skills/ai-sdk/references/devtools.md b/packages/browseros-agent/.agents/skills/ai-sdk/references/devtools.md new file mode 100644 index 000000000..197e203ad --- /dev/null +++ b/packages/browseros-agent/.agents/skills/ai-sdk/references/devtools.md @@ -0,0 +1,52 @@ +--- +title: AI SDK DevTools +description: Debug AI SDK calls by inspecting captured runs and steps. +--- + +# AI SDK DevTools + +## Why Use DevTools + +DevTools captures all AI SDK calls (`generateText`, `streamText`, `ToolLoopAgent`) to a local JSON file. This lets you inspect LLM requests, responses, tool calls, and multi-step interactions without manually logging. + +## Setup + +Requires AI SDK 6. Install `@ai-sdk/devtools` using your project's package manager. + +Wrap your model with the middleware: + +```ts +import { wrapLanguageModel, gateway } from 'ai'; +import { devToolsMiddleware } from '@ai-sdk/devtools'; + +const model = wrapLanguageModel({ + model: gateway('anthropic/claude-sonnet-4.5'), + middleware: devToolsMiddleware(), +}); +``` + +## Viewing Captured Data + +All runs and steps are saved to: + +``` +.devtools/generations.json +``` + +Read this file directly to inspect captured data: + +```bash +cat .devtools/generations.json | jq +``` + +Or launch the web UI: + +```bash +npx @ai-sdk/devtools +# Open http://localhost:4983 +``` + +## Data Structure + +- **Run**: A complete multi-step interaction grouped by initial prompt +- **Step**: A single LLM call within a run (includes input, output, tool calls, token usage) diff --git a/packages/browseros-agent/.agents/skills/ai-sdk/references/type-safe-agents.md b/packages/browseros-agent/.agents/skills/ai-sdk/references/type-safe-agents.md new file mode 100644 index 000000000..17d7a6fdc --- /dev/null +++ b/packages/browseros-agent/.agents/skills/ai-sdk/references/type-safe-agents.md @@ -0,0 +1,204 @@ +--- +title: Type-Safe useChat with Agents +description: Build end-to-end type-safe agents by inferring UIMessage types from your agent definition. +--- + +# Type-Safe useChat with Agents + +Build end-to-end type-safe agents by inferring `UIMessage` types from your agent definition for type-safe UI rendering with `useChat`. + +## Recommended Structure + +``` +lib/ + agents/ + my-agent.ts # Agent definition + type export + tools/ + weather-tool.ts # Individual tool definitions + calculator-tool.ts +``` + +## Define Tools + +```ts +// lib/tools/weather-tool.ts +import { tool } from 'ai'; +import { z } from 'zod'; + +export const weatherTool = tool({ + description: 'Get current weather for a location', + inputSchema: z.object({ + location: z.string().describe('City name'), + }), + execute: async ({ location }) => { + return { temperature: 72, condition: 'sunny', location }; + }, +}); +``` + +## Define Agent and Export Type + +```ts +// lib/agents/my-agent.ts +import { ToolLoopAgent, InferAgentUIMessage } from 'ai'; +import { weatherTool } from '../tools/weather-tool'; +import { calculatorTool } from '../tools/calculator-tool'; + +export const myAgent = new ToolLoopAgent({ + model: 'anthropic/claude-sonnet-4', + instructions: 'You are a helpful assistant.', + tools: { + weather: weatherTool, + calculator: calculatorTool, + }, +}); + +// Infer the UIMessage type from the agent +export type MyAgentUIMessage = InferAgentUIMessage; +``` + +### With Custom Metadata + +```ts +// lib/agents/my-agent.ts +import { z } from 'zod'; + +const metadataSchema = z.object({ + createdAt: z.number(), + model: z.string().optional(), +}); + +type MyMetadata = z.infer; + +export type MyAgentUIMessage = InferAgentUIMessage; +``` + +## Use with `useChat` + +```tsx +// app/chat.tsx +import { useChat } from '@ai-sdk/react'; +import type { MyAgentUIMessage } from '@/lib/agents/my-agent'; + +export function Chat() { + const { messages } = useChat(); + + return ( +
+ {messages.map(message => ( + + ))} +
+ ); +} +``` + +## Rendering Parts with Type Safety + +Tool parts are typed as `tool-{toolName}` based on your agent's tools: + +```tsx +function Message({ message }: { message: MyAgentUIMessage }) { + return ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case 'text': + return

{part.text}

; + + case 'tool-weather': + // part.input and part.output are fully typed + if (part.state === 'output-available') { + return ( +
+ Weather in {part.input.location}: {part.output.temperature}F +
+ ); + } + return
Loading weather...
; + + case 'tool-calculator': + // TypeScript knows this is the calculator tool + return
Calculating...
; + + default: + return null; + } + })} +
+ ); +} +``` + +The `part.type` discriminant narrows the type, giving you autocomplete and type checking for `input` and `output` based on each tool's schema. + +## Splitting Tool Rendering into Components + +When rendering many tools, you may want to split each tool into its own component. Use `UIToolInvocation` to derive a typed invocation from your tool and export it alongside the tool definition: + +```ts +// lib/tools/weather-tool.ts +import { tool, UIToolInvocation } from 'ai'; +import { z } from 'zod'; + +export const weatherTool = tool({ + description: 'Get current weather for a location', + inputSchema: z.object({ + location: z.string().describe('City name'), + }), + execute: async ({ location }) => { + return { temperature: 72, condition: 'sunny', location }; + }, +}); + +// Export the invocation type for use in UI components +export type WeatherToolInvocation = UIToolInvocation; +``` + +Then import only the type in your component: + +```tsx +// components/weather-tool.tsx +import type { WeatherToolInvocation } from '@/lib/tools/weather-tool'; + +export function WeatherToolComponent({ + invocation, +}: { + invocation: WeatherToolInvocation; +}) { + // invocation.input and invocation.output are fully typed + if (invocation.state === 'output-available') { + return ( +
+ Weather in {invocation.input.location}: {invocation.output.temperature}F +
+ ); + } + return
Loading weather for {invocation.input?.location}...
; +} +``` + +Use the component in your message renderer: + +```tsx +function Message({ message }: { message: MyAgentUIMessage }) { + return ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case 'text': + return

{part.text}

; + case 'tool-weather': + return ; + case 'tool-calculator': + return ; + default: + return null; + } + })} +
+ ); +} +``` + +This approach keeps your tool rendering logic organized while maintaining full type safety, without needing to import the tool implementation into your UI components. diff --git a/packages/browseros-agent/.claude/commands/browseros-review.md b/packages/browseros-agent/.claude/commands/browseros-review.md new file mode 100644 index 000000000..1bae6ba4c --- /dev/null +++ b/packages/browseros-agent/.claude/commands/browseros-review.md @@ -0,0 +1,215 @@ +# BrowserOS Code Review + +You are a code review specialist. Identify genuine issues while filtering out false positives. + +## Core Principles + +- **High Signal Only**: Flag only issues you are certain about +- **Objective Over Subjective**: Focus on bugs, security, explicit CLAUDE.md violations +- **Validate Everything**: Every issue must be validated before reporting + +## Execution Steps + +### Step 1: Determine Review Scope + +**If argument provided** (e.g., `$ARGUMENTS` is a PR number): Proceed with PR review. + +**If no argument provided**: Ask the user ONE question with TWO options: +1. **PR Review** - then ask for PR number in follow-up +2. **Branch Diff** - proceed immediately (no follow-up needed) + +**For PR review:** +```bash +gh pr view --json title,body,files,baseRefName,headRefName +gh pr diff +``` + +**For branch diff (changes since fork point, not current main):** +```bash +# Find where branch forked from main +MERGE_BASE=$(git merge-base main HEAD) +git diff $MERGE_BASE HEAD +git log $MERGE_BASE..HEAD --oneline +``` + +### Step 2: Initial Screening + +Launch a haiku agent to check if any of the following are true: +- The pull request is closed +- The pull request is a draft +- The pull request does not need code review (e.g. automated PR, trivial change that is obviously correct) +- Claude has already commented on this PR (check `gh pr view --comments` for comments left by claude) + +If any condition is true, stop and do not proceed. + +Note: Still review Claude generated PR's. + +### Step 3: Documentation Discovery + +Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including: +- The root CLAUDE.md file, if it exists +- Any CLAUDE.md files in directories containing files modified by the pull request + +### Step 4: Change Summary + +Launch a sonnet agent to view the pull request and return a summary of the changes. + +### Step 5: Parallel Multi-Agent Review + +Launch 7 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. "CLAUDE.md adherence", "bug", "design principle", "readability"). The agents should do the following: + +**Agents 1 + 2: CLAUDE.md compliance sonnet agents** +Audit changes for CLAUDE.md compliance in parallel. Note: When evaluating CLAUDE.md compliance for a file, you should only consider CLAUDE.md files that share a file path with the file or parents. + +**Agent 3: Opus bug agent (parallel subagent with agent 4)** +Scan for obvious bugs. Focus only on the diff itself without reading extra context. Flag only significant bugs; ignore nitpicks and likely false positives. Do not flag issues that you cannot validate without looking at context outside of the git diff. + +**Agent 4: Opus bug agent (parallel subagent with agent 3)** +Look for problems that exist in the introduced code. This could be security issues, incorrect logic, etc. Only look for issues that fall within the changed code. + +**Agent 5: Design principles Opus agent** +Flag clear violations of: +- **SRP**: Class/module doing multiple unrelated things +- **DRY**: Duplicated logic that should be extracted +- **Separation of Concerns**: Business logic mixed with data access/UI/transport +- **KISS**: Unnecessary complexity, over-abstraction +- **YAGNI**: Unused features, speculative generalization + +**Agent 6: Design patterns Opus agent** +When code would clearly benefit, suggest these patterns: +- **Factory**: Scattered/duplicated object creation → centralize with factory +- **Builder**: Constructor with 4+ params or complex setup → step-by-step builder +- **Strategy**: Multiple if/else chains selecting behavior → interchangeable strategies +- **Decorator**: Need to add behavior dynamically → wrap objects with decorators +- **Observer**: Objects need to react to state changes → pub/sub notification +- **Repository**: Data access mixed with business logic → abstract data layer +- **Singleton**: Need exactly one instance → controlled single instance +- **Adapter**: Incompatible interfaces → wrapper to make compatible +- **Dependency Injection**: Hard-coded dependencies → inject via constructor +- **MVC**: Mixed data/UI/logic → separate model, view, controller + +Only suggest when the pattern clearly solves an existing problem in the code. Don't suggest patterns speculatively. + +**Agent 7: Code readability & type safety sonnet agent** +Flag these readability issues: +- Functions over 100 lines +- Nesting depth > 3 levels +- Unclear names requiring mental mapping +- Magic numbers/strings without named constants +- God objects/files doing too many things + +Flag these type safety issues (TypeScript): +- **Untyped functions**: Function parameters or return types using `any` instead of proper types +- **Inline `any` casts**: Callbacks like `(x: any) => ...` that bypass type checking +- **Missing interfaces for external data**: JSON parsing, API responses, or third-party data without defined types (create interfaces even for complex/nested structures) +- Non-null assertions (!) without validation +- Type narrowing lost across async boundaries + +**CRITICAL: We only want HIGH SIGNAL issues.** This means: +- Objective bugs that will cause incorrect behavior at runtime +- Clear, unambiguous CLAUDE.md violations where you can quote the exact rule being broken +- Design issues that clearly harm maintainability (not speculative concerns) + +We do NOT want: +- Subjective concerns or "suggestions" +- Style preferences not explicitly required by CLAUDE.md +- Potential issues that "might" be problems +- Anything requiring interpretation or judgment calls + +If you are not certain an issue is real, do not flag it. False positives erode trust and waste reviewer time. + +In addition to the above, each subagent should be told the PR title and description. This will help provide context regarding the author's intent. + +### Step 6: Issue Validation + +For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. These subagents should get the PR title and description along with a description of the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as "variable is not defined" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations. + +Filter out any issues that were not validated. This step will give us our list of high signal issues for our review. + +## DO NOT Flag (False Positives) + +- Pre-existing issues not introduced in this diff +- Correct code that appears buggy without context +- Pedantic nitpicks a senior engineer wouldn't flag +- Issues a linter will catch (Biome handles these) +- Issues silenced in code (lint ignore comments) +- Subjective suggestions or "might be" problems +- Style preferences not explicitly in CLAUDE.md +- General quality concerns unless explicitly in CLAUDE.md + +## Comment Guidelines + +- One comment per unique issue +- For fixes under 5 lines: include committable suggestion block +- For fixes 6+ lines: provide high-level guidance + copyable prompt +- Never include fixes that would break without additional changes + +**Suggestions must be COMPLETE.** If a fix requires additional changes elsewhere (e.g., renaming a variable requires updating all usages), do NOT use a suggestion block. The author should be able to click "Commit suggestion" and have a working fix - no followup work required. + +For larger fixes (6+ lines, structural changes, or changes spanning multiple locations), do NOT use suggestion blocks. Instead: +1. Describe what the issue is +2. Explain the suggested fix at a high level +3. Include a copyable prompt for Claude Code: + ``` + Fix [file:line]: [brief description of issue and suggested fix] + ``` + +## Output Format + +After reviewing, output a concise summary: + +``` +## Code Review Summary + +**Scope**: [PR #123 / Branch `feat/xyz` vs main] +**Files reviewed**: [count] + +### Bugs & Logic Issues + +For each issue: +- **[SEVERITY]** `file:line` - Brief description + - Why it's a problem + - Suggested fix (or copyable prompt: `Fix file:line: description`) + +### Type Safety Issues + +For each issue: +- **[SEVERITY]** `file:line` - Brief description (e.g., "10 functions use `any` instead of typed interfaces") + - Why it's a problem + - Suggested fix + +### Readability Issues + +For each issue: +- **[SEVERITY]** `file:line` - Brief description (e.g., "Function exceeds 100 lines", "Nesting depth > 3") + - Why it's a problem + - Suggested fix + +### Design Pattern Suggestions + +For each suggestion: +- **[PATTERN]** `file:line` - Where and why to apply + - Current problem (e.g., "5 if/else branches selecting behavior") + - Suggested pattern and brief implementation guidance + +### Concise Action Items + +🔴 HIGH PRIORITY: +□ [Critical bugs, security issues, data loss risks] + +🟡 MEDIUM PRIORITY: +□ [Type safety issues, design principle violations, readability issues] + +🟢 SUGGESTIONS: +□ [Design pattern recommendations] +``` + +If no issues found, output: +``` +## Code Review Summary + +**Scope**: [PR #123 / Branch vs main] +**Files reviewed**: [count] + +No issues found. Code looks good. +``` diff --git a/packages/browseros-agent/.claude/skills/dev-debug/SKILL.md b/packages/browseros-agent/.claude/skills/dev-debug/SKILL.md new file mode 100644 index 000000000..b8ae0673a --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev-debug/SKILL.md @@ -0,0 +1,69 @@ +--- +name: dev-debug +description: Debug an issue by identifying root causes, fixing the most probable one, testing, and committing. Use with "/dev-debug ". +disable-model-invocation: true +argument-hint: [issue description] +--- + +# Debug Workflow + +You are debugging an issue. Be methodical — understand before you fix. + +## Step 1: Understand the issue + +1. Derive a short slug from the issue description (e.g., "login fails after redirect" → `login_redirect_fail`) +2. Create `.llm/debug_/` directory +3. Read the relevant code paths. Trace the logic that relates to the issue described in `$ARGUMENTS`. +4. Identify **2-5 possible root causes**, ranked by probability. + +Write your analysis to `.llm/debug_/tmp_root_causes.md`: + +``` +## Issue + + +## Root Causes (ranked by probability) + +### 1. [Most likely] — +- **Why**: Explanation of why this could be the cause +- **Evidence**: What in the code supports this theory +- **Files**: Relevant file paths + +### 2. +... +``` + +Present the root causes to the user. Ask if they agree with the ranking or want to override which one to fix first. + +## Step 2: Fix the most probable root cause + +After user confirms (or you proceed with #1 by default): + +1. Implement the fix — keep it minimal and focused. Only change what's needed to address the root cause. +2. Follow existing code patterns and conventions. +3. No drive-by refactors — fix the bug, nothing else. + +## Step 3: Test + +1. Run existing tests if they cover the affected code path. +2. If no tests exist or tests don't cover this case, tell the user what to test manually and what the expected behavior should be. +3. If tests fail, fix and re-test in a loop. + +## Step 4: Commit + +Stage and commit the fix: + +```bash +git add -A && git commit -m "fix: " +``` + +## Step 5: Check for remaining issues + +If the fix didn't fully resolve the issue (or if there are related problems): +1. Update `.llm/debug_/tmp_root_causes.md` — mark #1 as addressed, re-rank remaining causes +2. Go back to Step 2 with the next root cause +3. Commit after each fix + +When the issue is resolved, tell the user: + +> Debug complete. Fixed in N commit(s). See `.llm/debug_/tmp_root_causes.md` for the full analysis. diff --git a/packages/browseros-agent/.claude/skills/dev/SKILL.md b/packages/browseros-agent/.claude/skills/dev/SKILL.md new file mode 100644 index 000000000..6bb5a1713 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev/SKILL.md @@ -0,0 +1,26 @@ +--- +name: dev +description: Full feature development workflow. Explores codebase, designs, writes PRD, implements, reviews, fixes, and creates PR. Use with "/dev ". +disable-model-invocation: true +argument-hint: [feature description] +--- + +# Dev Workflow — Orchestrator + +Run the full feature development pipeline by invoking sub-skills sequentially. Start by invoking `/dev1-start $ARGUMENTS`. Each sub-skill will automatically chain to the next one. + +## Pipeline + +1. `/dev1-start` — High-level code exploration +2. `/dev2-design` — Design options (user picks one) +3. `/dev3-prd` — Questions + PRD +4. `/dev4-implement` — Implementation +5. `/dev5-review` — Code review + commit +6. `/dev6-review-fix` — Apply review fixes + commit (skipped if review is clean) +7. `/dev7-pr` — Create PR, wait for Greptile, address comments, push + +## Instructions + +Invoke `/dev1-start $ARGUMENTS` now. It will chain through the rest of the pipeline automatically. Each skill writes artifacts to `.llm//` and hands off to the next skill. + +Skills that need user input (design choice, question answers, PR approval) will pause and wait before continuing. diff --git a/packages/browseros-agent/.claude/skills/dev1-start/SKILL.md b/packages/browseros-agent/.claude/skills/dev1-start/SKILL.md new file mode 100644 index 000000000..e59b6e6d5 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev1-start/SKILL.md @@ -0,0 +1,40 @@ +--- +name: dev1-start +description: Start a new feature development. Does a high-level exploration of the codebase to understand the stack and project layout, then kicks off the design phase. Sub-skill of the /dev workflow. +argument-hint: [feature description] +--- + +# Dev Workflow — Step 1: Start & Explore + +You are beginning a new feature development workflow. Do a quick, high-level exploration to understand the stack, project layout, and what already exists. This is not a deep dive — just orient yourself. + +## Step 1: Set up the feature workspace + +1. Derive a short snake_case feature name from the user's description (e.g., "add user auth" → `add_user_auth`) +2. Create the directory `.llm//` +3. Write `.llm//tmp_context.md` with: + - The original feature request (verbatim from `$ARGUMENTS`) + - Timestamp + - Current working directory + +## Step 2: High-level code exploration + +Get a quick lay of the land: + +1. **Read CLAUDE.md** — If there is a `CLAUDE.md` (or `.claude/CLAUDE.md`) in the project root, read it first. It contains project-specific instructions and context. +2. **Understand the stack** — What language(s), frameworks, and key dependencies does this project use? Check `package.json`, `Cargo.toml`, `go.mod`, or equivalent. +3. **Map the project structure** — List top-level directories and what each one is for. If it's a monorepo, identify the different packages/apps. +4. **Identify high-level features** — What does this project do? What are its main features or entry points? + +Write your learnings to `.llm//tmp_exploration.md`. Keep it concise — bullet points are fine. + +## Step 3: Summarize for the user + +Present a brief summary to the user: +- The stack and project structure +- Key projects/packages if it's a monorepo +- Where the new feature likely fits in + +## Step 4: Hand off + +Tell the user the exploration is complete, then immediately invoke `/dev2-design ` (where `` is the slug you created in Step 1). diff --git a/packages/browseros-agent/.claude/skills/dev2-design/SKILL.md b/packages/browseros-agent/.claude/skills/dev2-design/SKILL.md new file mode 100644 index 000000000..1dbcaf277 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev2-design/SKILL.md @@ -0,0 +1,50 @@ +--- +name: dev2-design +description: Generate high-level design options for a feature. Presents 2-4 design alternatives with pros and cons. Sub-skill of the /dev workflow. +argument-hint: [feature_name] +--- + +# Dev Workflow — Step 2: Design Options + +You are generating design options for a feature. Think like a staff software engineer at Google presenting options in a design review. + +## Input + +1. Read `.llm/$ARGUMENTS/tmp_context.md` for the original feature request +2. Read `.llm/$ARGUMENTS/tmp_exploration.md` for codebase exploration findings + +## Step 1: Generate design options + +Present **2 to 4 high-level design options**. For each option: + +- **Name** — A short descriptive title (e.g., "Option A: Event-driven with pub/sub") +- **Overview** — 2-3 paragraphs explaining the approach at a high level. NO code snippets. Describe the architecture, data flow, and key decisions in plain language. +- **Advantages** — Bullet list of pros +- **Disadvantages** — Bullet list of cons +- **Complexity** — Low / Medium / High +- **Risk** — Low / Medium / High + +If there is genuinely only one reasonable way to implement the feature, present that single option and explain why alternatives don't make sense. + +## Rules + +- **No code snippets** in design options. This is a high-level architectural discussion. +- Design options need not be very futureproof; prioritize solving the immediate need over speculative extensibility. +- Focus on trade-offs: maintainability vs performance, simplicity vs flexibility, etc. +- Ground each option in the actual codebase — reference real modules, patterns, and conventions found during exploration. +- Be honest about disadvantages. Don't present a straw man option just to fill the count. + +## Step 2: Present and get user's choice + +Present all options to the user in a clear format. Ask the user which design option they prefer (or if they want to combine aspects of multiple options). + +## Step 3: Save the chosen design + +After the user picks an option, write `.llm/$ARGUMENTS/design.md` with: +- The chosen design option (full description) +- Any user modifications or clarifications +- Key decisions and rationale + +## Step 4: Hand off + +Tell the user the design is locked in, then immediately invoke `/dev3-prd $ARGUMENTS`. diff --git a/packages/browseros-agent/.claude/skills/dev3-prd/SKILL.md b/packages/browseros-agent/.claude/skills/dev3-prd/SKILL.md new file mode 100644 index 000000000..1ca00caf1 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev3-prd/SKILL.md @@ -0,0 +1,60 @@ +--- +name: dev3-prd +description: Generate questions, clarify unknowns, then write a PRD using pyramid principles. Sub-skill of the /dev workflow. +argument-hint: [feature_name] +--- + +# Dev Workflow — Step 3: Questions & PRD + +You are writing a Product Requirements Document (PRD) / design spec. Before writing, you must clarify all unknowns. + +## Input + +1. Read `.llm/$ARGUMENTS/tmp_context.md` for the original feature request +2. Read `.llm/$ARGUMENTS/tmp_exploration.md` for codebase exploration findings +3. Read `.llm/$ARGUMENTS/design.md` for the chosen design + +## Step 1: Generate questions + +Think about what a staff software engineer would need clarified before writing a design spec. Write your questions to `.llm/$ARGUMENTS/tmp_questions.md`. + +For each question: +- Write the question clearly +- Attempt to answer it yourself by reading the codebase +- Mark each question as either `[RESOLVED]` (you found the answer in the code) or `[NEEDS INPUT]` (requires human clarification) + +## Step 2: Ask the human + +If there are any `[NEEDS INPUT]` questions remaining, present ONLY those to the user. Wait for their answers. Update `.llm/$ARGUMENTS/tmp_questions.md` with the answers. + +If all questions were self-resolved, tell the user: "No open questions — all clarified from the codebase. Proceeding to PRD." + +## Step 3: Write the PRD + +Write the PRD to `.llm/$ARGUMENTS/prd.md` following the **pyramid principle** in three levels: + +### Level 1 — Executive Summary + +1. **Requirements** — Clear bullet points listing what the feature must do. Just the requirements, nothing else. +2. **Background** — What are we building and why? Context and motivation in 2-3 paragraphs. +3. **Design Overview** — High-level overview of the chosen design. How the pieces fit together. No code. + +### Level 2 — Component Details + +For each major component or module in the design: +- **One paragraph** explaining what this component does, its responsibilities, and how it interacts with other components. + +### Level 3 — Implementation Details + +For each component from Level 2: +- Code snippets showing key interfaces, data structures, or function signatures +- Specific file paths where changes will be made +- Any migration or configuration changes needed + +## Step 4: Present and confirm + +Show the user a summary of the PRD (Level 1 only) and ask if they want to review the full document or proceed. + +## Step 5: Hand off + +Tell the user the PRD is complete, then immediately invoke `/dev4-implement $ARGUMENTS`. diff --git a/packages/browseros-agent/.claude/skills/dev4-implement/SKILL.md b/packages/browseros-agent/.claude/skills/dev4-implement/SKILL.md new file mode 100644 index 000000000..7f33e66a9 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev4-implement/SKILL.md @@ -0,0 +1,65 @@ +--- +name: dev4-implement +description: Implement a feature from its PRD. Creates a work tree if needed, writes clean code following Google-level standards, and tests iteratively. Sub-skill of the /dev workflow. +argument-hint: [feature_name] +--- + +# Dev Workflow — Step 4: Implement + +You are implementing a feature from its PRD. Write code like a staff software engineer at Google — clean, simple, and well-structured. + +## Input + +1. Read `.llm/$ARGUMENTS/prd.md` for the full PRD +2. Read `.llm/$ARGUMENTS/design.md` for design decisions +3. Read `.llm/$ARGUMENTS/tmp_exploration.md` for codebase context + +## Step 1: Set up work tree + +Check if you are already in a git work tree (not the main tree): +```bash +git rev-parse --is-inside-work-tree && git worktree list +``` + +If you are in the **main work tree** (not a feature worktree), create one: +```bash +wt switch -c feat/$ARGUMENTS +``` + +If already in a feature worktree, continue in place. + +## Step 2: Plan implementation order + +Break the PRD into small, testable implementation steps. Write the plan to `.llm/$ARGUMENTS/tmp_impl_plan.md`. Each step should be: +- Small enough to verify independently +- Ordered so that dependencies come first +- Testable (you can run something to verify it works) + +## Step 3: Implement step by step + +For each step in the plan: +1. Write the code +2. Test it (run existing tests, or manually verify) +3. Fix any failures before moving to the next step + +## Code Style Guide + +Follow these rules strictly: + +- **No excessive console.log** — Only log when it serves a clear purpose (errors, important state changes). Remove debug logs. +- **Self-contained functions** — Each function should do one thing. No function should exceed 20-30 lines. +- **Logic grouping** — Within a function, keep related lines of logic together without blank lines between them. Use a blank line only to separate distinct logical blocks. +- **Comments** — Only add a comment when the logic is not self-evident. The comment should explain *why*, not *what*. Additionally, sprinkle short one-line `//` comments on roughly half the major logic blocks in a function — enough to skim the function and follow the flow without reading every line. Keep these brief (e.g., `// validate input`, `// build response payload`). Not every block needs one, but the big chunks should be signposted. +- **Simple and direct** — No premature abstractions. No over-engineering. Write the simplest code that solves the problem. +- **Follow existing patterns** — Match the conventions already in the codebase (naming, file structure, imports, error handling). + +## Step 4: Verify + +After all steps are implemented: +1. Run the full test suite +2. Manually verify the feature works as described in the PRD +3. Fix anything that fails — loop back to implementation until it passes + +## Step 5: Hand off + +Tell the user implementation is complete, then immediately invoke `/dev5-review $ARGUMENTS`. diff --git a/packages/browseros-agent/.claude/skills/dev5-review/SKILL.md b/packages/browseros-agent/.claude/skills/dev5-review/SKILL.md new file mode 100644 index 000000000..6eaea461e --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev5-review/SKILL.md @@ -0,0 +1,80 @@ +--- +name: dev5-review +description: Review implemented code for quality, correctness, and style. Produces review comments and creates a commit. Sub-skill of the /dev workflow. +argument-hint: [feature_name] +--- + +# Dev Workflow — Step 5: Code Review + +You are reviewing code like a senior engineer doing a thorough code review. Be constructive but rigorous. + +## Input + +1. Read `.llm/$ARGUMENTS/prd.md` for what the feature should do +2. Read `.llm/$ARGUMENTS/design.md` for the chosen design +3. Run `git diff` to see all changes made during implementation + +## Step 1: Style guide review + +If the project uses TypeScript, invoke `/ts-style-review` to check all changed files against the Google TypeScript Style Guide and team conventions. Incorporate its findings into your review comments. + +## Step 2: Review the code + +Review every changed file. Check for: + +### Correctness +- Does the implementation match the PRD requirements? +- Are there edge cases not handled? +- Are there logical errors? + +### Code Quality +- Functions under 20-30 lines? +- Logic grouped without unnecessary blank lines? +- No excessive console.log statements? +- Comments only where logic is non-obvious (explaining *why*, not *what*)? +- Self-contained functions that do one thing? + +### Architecture +- Does it follow existing codebase patterns? +- Are there unnecessary abstractions or over-engineering? +- Is the code simple and direct? + +### Safety +- Any security issues (injection, XSS, etc.)? +- Proper error handling at system boundaries? +- No leaked secrets or credentials? + +## Step 3: Write review comments + +Write review comments to `.llm/$ARGUMENTS/tmp_review.md` in this format: + +``` +## Review Comments + +### [file_path:line_number] — severity (critical/suggestion/nit) +Description of the issue and suggested fix. + +### [file_path:line_number] — severity +... +``` + +## Step 4: Present review to user + +Show the user a summary: +- Total files reviewed +- Number of critical / suggestion / nit comments +- Top 3 most important issues (if any) + +## Step 5: Commit + +Stage all changes and create a commit with a clear, descriptive commit message that summarizes the feature: + +```bash +git add -A && git commit -m "feat: " +``` + +## Step 6: Hand off + +Tell the user the review summary, then: +- If there are critical or suggestion comments: immediately invoke `/dev6-review-fix $ARGUMENTS` +- If there are zero actionable comments (only nits or clean code): skip dev6 and immediately invoke `/dev7-pr $ARGUMENTS` diff --git a/packages/browseros-agent/.claude/skills/dev6-review-fix/SKILL.md b/packages/browseros-agent/.claude/skills/dev6-review-fix/SKILL.md new file mode 100644 index 000000000..98e7afd6b --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev6-review-fix/SKILL.md @@ -0,0 +1,44 @@ +--- +name: dev6-review-fix +description: Apply fixes for review comments from dev5-review and commit. Sub-skill of the /dev workflow. +argument-hint: [feature_name] +--- + +# Dev Workflow — Step 6: Fix Review Comments + +You are applying fixes for the code review comments. + +## Input + +1. Read `.llm/$ARGUMENTS/tmp_review.md` for the review comments +2. Read `.llm/$ARGUMENTS/prd.md` for context on what the feature should do + +## Step 1: Triage comments + +Read all review comments. Group them: +- **Critical** — Must fix. Bugs, security issues, correctness problems. +- **Suggestions** — Should fix. Code quality, readability improvements. +- **Nits** — Fix if quick. Minor style preferences. + +## Step 2: Apply fixes + +For each comment (starting with critical, then suggestions, then nits): +1. Read the referenced file and line +2. Apply the fix +3. Verify the fix doesn't break anything (run tests if applicable) + +## Step 3: Verify + +Run the test suite to make sure fixes didn't introduce regressions. + +## Step 4: Commit + +Stage and commit the review fixes: + +```bash +git add -A && git commit -m "fix: address review comments for $ARGUMENTS" +``` + +## Step 5: Hand off + +Tell the user review fixes are applied and committed, then immediately invoke `/dev7-pr $ARGUMENTS`. diff --git a/packages/browseros-agent/.claude/skills/dev7-pr/SKILL.md b/packages/browseros-agent/.claude/skills/dev7-pr/SKILL.md new file mode 100644 index 000000000..8032e4192 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/dev7-pr/SKILL.md @@ -0,0 +1,88 @@ +--- +name: dev7-pr +description: Create a PR, push to GitHub, wait for Greptile review, address comments, and push final. Sub-skill of the /dev workflow. +argument-hint: [feature_name] +--- + +# Dev Workflow — Step 7: PR & External Review + +You are creating a pull request, waiting for automated review (Greptile), and addressing any comments. + +## Input + +1. Read `.llm/$ARGUMENTS/prd.md` for the feature summary +2. Read `.llm/$ARGUMENTS/design.md` for design context +3. Run `git log --oneline -10` to see recent commits + +## Step 1: Push and create PR + +Push the current branch and create a PR: + +```bash +git push -u origin HEAD +``` + +Then create the PR using the PRD Level 1 summary as the body: + +```bash +gh pr create --title "feat: " --body "" +``` + +The PR body should include: +- **Summary** — 2-3 bullet points from the PRD requirements +- **Design** — One paragraph on the chosen approach +- **Test plan** — How to verify the feature works + +## Step 2: Wait for Greptile review + +Tell the user: "Waiting 10 minutes for Greptile to review the PR..." + +Start a timer: + +```bash +echo "Waiting for Greptile review..." && sleep 600 && echo "Timer complete — checking for PR comments." +``` + +## Step 3: Pull PR comments + +After the timer, fetch PR comments: + +```bash +gh pr view --comments +``` + +Also check the review comments: + +```bash +gh api repos/{owner}/{repo}/pulls/{pr_number}/comments +``` + +## Step 4: Address PR comments + +If there are review comments from Greptile or other reviewers: +1. Read each comment +2. Apply the fix to the relevant file +3. Keep fixes focused — only change what the comment asks for + +If there are no comments, skip to Step 5. + +## Step 5: Commit and push + +If fixes were made: + +```bash +git add -A && git commit -m "fix: address PR review comments for $ARGUMENTS" && git push +``` + +## Step 6: Done + +Tell the user: + +> PR created and review comments addressed. The feature is ready for human review. +> PR URL: + +Write a final summary to `.llm/$ARGUMENTS/tmp_done.md` with: +- PR URL +- Total commits +- Summary of what was built +- Any follow-up items or tech debt noted during review diff --git a/packages/browseros-agent/.claude/skills/ts-style-review/SKILL.md b/packages/browseros-agent/.claude/skills/ts-style-review/SKILL.md new file mode 100644 index 000000000..211ab2481 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/ts-style-review/SKILL.md @@ -0,0 +1,87 @@ +--- +name: ts-style-review +description: Review TypeScript code against the Google TypeScript Style Guide plus team conventions. Use when reviewing TS code for style, naming, imports, type usage, or quality. +--- + +# TypeScript Style Guide Review + +When reviewing TypeScript code, check against these sections. For full rules and examples, see [google-ts-styleguide.md](google-ts-styleguide.md). + +## 1. Source File Structure +- File order: copyright, `@fileoverview`, imports, implementation +- Use named exports only — no default exports +- Use ES6 modules — no `namespace`, no `require()` +- Prefer namespace imports for large APIs, named imports for frequently used symbols +- No mutable exports (`export let`) — use getter functions instead +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 3. Source file structure + +## 2. Variables & Literals +- `const` by default, `let` when reassigned, never `var` +- One variable per declaration +- Single quotes for strings, template literals for concatenation +- No `Array()` or `Object()` constructors +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 4. Language features + +## 3. Classes +- Use `readonly` for properties never reassigned after constructor +- Use parameter properties (`constructor(private readonly foo: Foo)`) +- No `public` keyword — TypeScript is public by default +- No `#private` fields — use `private` modifier +- No prototype manipulation +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 4. Classes + +## 4. Functions +- Prefer function declarations for named functions +- Arrow functions for callbacks and nested functions — never function expressions +- Use rest parameters, not `arguments` +- Use spread syntax, not `Function.prototype.apply` +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 4. Functions + +## 5. Control Flow & Error Handling +- Always use braces for control flow blocks +- Always `===` and `!==` (exception: `== null` for null/undefined check) +- `for...of` for arrays, never `for...in` +- Only throw `Error` instances (or subclasses), never strings +- All `switch` must have `default`, no fall-through +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 4. Control structures + +## 6. Naming +- `UpperCamelCase`: classes, interfaces, types, enums, decorators, TSX components +- `lowerCamelCase`: variables, parameters, functions, methods, properties +- `CONSTANT_CASE`: global constants, enum values +- Descriptive names — no ambiguous abbreviations +- Treat acronyms as words (`loadHttpUrl`, not `loadHTTPURL`) +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 5. Naming + +## 7. Type System +- Prefer `interface` over `type` alias for object shapes +- Use `unknown` over `any` — narrow with type guards +- Use optional (`?`) over `| undefined` +- `T[]` for simple types, `Array` for complex +- No wrapper types (`String`, `Boolean`, `Number`) — use primitives +- No `@ts-ignore` or `@ts-nocheck` +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 6. Type system + +## 8. Schema & Type Definitions (Team Convention) +- Use **Zod** for defining schemas, then derive TypeScript types with `z.infer` +- If types are **not shared** across files, define the Zod schema and inferred type at the **top of the same file** that uses them +- If types **are shared**, place them in a shared types/schemas module +- Example: + ```ts + import { z } from 'zod'; + + const UserSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + }); + + type User = z.infer; + ``` + +## 9. Comments & Documentation +- `/** JSDoc */` for public API documentation +- `//` for implementation comments +- No type annotations in JSDoc — TypeScript handles types +- Mark deprecated code with `@deprecated` +- See [google-ts-styleguide.md](google-ts-styleguide.md) § 8. Comments and documentation diff --git a/packages/browseros-agent/.claude/skills/ts-style-review/google-ts-styleguide.md b/packages/browseros-agent/.claude/skills/ts-style-review/google-ts-styleguide.md new file mode 100644 index 000000000..8c2d07672 --- /dev/null +++ b/packages/browseros-agent/.claude/skills/ts-style-review/google-ts-styleguide.md @@ -0,0 +1,821 @@ +Here is the Google TypeScript Style Guide converted to Markdown. + +# Google TypeScript Style Guide + +This guide is based on the internal Google TypeScript style guide, but it has been slightly adjusted to remove Google-internal sections. Google's internal environment has different constraints on TypeScript than you might find outside of Google. The advice here is specifically useful for people authoring code they intend to import into Google, but otherwise may not apply in your external environment. + +There is no automatic deployment process for this version as it's pushed on-demand by volunteers. + +--- + +## 1. Introduction + +### Terminology notes + +This Style Guide uses [RFC 2119](https://tools.ietf.org/html/rfc2119) terminology when using the phrases *must*, *must not*, *should*, *should not*, and *may*. The terms *prefer* and *avoid* correspond to *should* and *should not*, respectively. Imperative and declarative statements are prescriptive and correspond to *must*. + +### Guide notes + +All examples given are **non-normative** and serve only to illustrate the normative language of the style guide. That is, while the examples are in Google Style, they may not illustrate the *only* stylish way to represent the code. Optional formatting choices made in examples must not be enforced as rules. + +## 2. Source file basics + +### File encoding: UTF-8 + +Source files are encoded in **UTF-8**. + +#### Whitespace characters + +Aside from the line terminator sequence, the ASCII horizontal space character (0x20) is the only whitespace character that appears anywhere in a source file. This implies that all other whitespace characters in string literals are escaped. + +#### Special escape sequences + +For any character that has a special escape sequence (`\'`, `\"`, `\\`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`), that sequence is used rather than the corresponding numeric escape (e.g `\x0a`, `\u000a`, or `\u{a}`). Legacy octal escapes are never used. + +#### Non-ASCII characters + +For the remaining non-ASCII characters, use the actual Unicode character (e.g. `∞`). For non-printable characters, the equivalent hex or Unicode escapes (e.g. `\u221e`) can be used along with an explanatory comment. + +**Good:** +```ts +// Perfectly clear, even without a comment. +const units = 'μs'; + +// Use escapes for non-printable characters. +const output = '\ufeff' + content; // byte order mark +``` + +**Bad:** +```ts +// Hard to read and prone to mistakes, even with the comment. +const units = '\u03bcs'; // Greek letter mu, 's' + +// The reader has no idea what this is. +const output = '\ufeff' + content; +``` + +## 3. Source file structure + +Files consist of the following, **in order**: + +1. Copyright information, if present +2. JSDoc with `@fileoverview`, if present +3. Imports, if present +4. The file’s implementation + +**Exactly one blank line** separates each section that is present. + +### Copyright information + +If license or copyright information is necessary in a file, add it in a JSDoc at the top of the file. + +### `@fileoverview` JSDoc + +A file may have a top-level `@fileoverview` JSDoc. If present, it may provide a description of the file's content, its uses, or information about its dependencies. Wrapped lines are not indented. + +**Example:** +```ts +/** + * @fileoverview Description of file. Lorem ipsum dolor sit amet, consectetur + * adipiscing elit, sed do eiusmod tempor incididunt. + */ +``` + +### Imports + +There are four variants of import statements in ES6 and TypeScript: + +| Import type | Example | Use for | +| :--- | :--- | :--- | +| module | `import * as foo from '...';` | TypeScript imports | +| named | `import {SomeThing} from '...';` | TypeScript imports | +| default | `import SomeThing from '...';` | Only for other external code that requires them | +| side-effect | `import '...';` | Only to import libraries for their side-effects on load (such as custom elements) | + +```ts +// Good: choose between two options as appropriate (see below). +import * as ng from '@angular/core'; +import {Foo} from './foo'; + +// Only when needed: default imports. +import Button from 'Button'; + +// Sometimes needed to import libraries for their side effects: +import 'jasmine'; +import '@polymer/paper-button'; +``` + +#### Import paths + +TypeScript code *must* use paths to import other TypeScript code. Paths *may* be relative, i.e. starting with `.` or `..`, or rooted at the base directory, e.g. `root/path/to/file`. + +Code *should* use relative imports (`./foo`) rather than absolute imports `path/to/foo` when referring to files within the same (logical) project as this allows to move the project around without introducing changes in these imports. + +Consider limiting the number of parent steps (`../../../`) as those can make module and path structures hard to understand. + +```ts +import {Symbol1} from 'path/from/root'; +import {Symbol2} from '../parent/file'; +import {Symbol3} from './sibling'; +``` + +#### Namespace versus named imports + +Both namespace and named imports can be used. + +Prefer named imports for symbols used frequently in a file or for symbols that have clear names, for example Jasmine's `describe` and `it`. Named imports can be aliased to clearer names as needed with `as`. + +Prefer namespace imports when using many different symbols from large APIs. A namespace import, despite using the `*` character, is not comparable to a "wildcard" import as seen in other languages. Instead, namespace imports give a name to all the exports of a module, and each exported symbol from the module becomes a property on the module name. Namespace imports can aid readability for exported symbols that have common names like `Model` or `Controller` without the need to declare aliases. + +**Bad:** +```ts +// Bad: overlong import statement of needlessly namespaced names. +import {Item as TableviewItem, Header as TableviewHeader, Row as TableviewRow, + Model as TableviewModel, Renderer as TableviewRenderer} from './tableview'; + +let item: TableviewItem|undefined; +``` + +**Good:** +```ts +// Better: use the module for namespacing. +import * as tableview from './tableview'; + +let item: tableview.Item|undefined; +``` + +**Bad:** +```ts +import * as testing from './testing'; + +// Bad: The module name does not improve readability. +testing.describe('foo', () => { + testing.it('bar', () => { + testing.expect(null).toBeNull(); + testing.expect(undefined).toBeUndefined(); + }); +}); +``` + +**Good:** +```ts +// Better: give local names for these common functions. +import {describe, it, expect} from './testing'; + +describe('foo', () => { + it('bar', () => { + expect(null).toBeNull(); + expect(undefined).toBeUndefined(); + }); +}); +``` + +**Special case: Apps JSPB protos** + +Apps JSPB protos must use named imports, even when it leads to long import lines. This rule exists to aid in build performance and dead code elimination. + +```ts +// Good: import the exact set of symbols you need from the proto file. +import {Foo, Bar} from './foo.proto'; + +function copyFooBar(foo: Foo, bar: Bar) {...} +``` + +#### Renaming imports + +Code *should* fix name collisions by using a namespace import or renaming the exports themselves. Code *may* rename imports (`import {SomeThing as SomeOtherThing}`) if needed. + +Three examples where renaming can be helpful: +1. If it's necessary to avoid collisions with other imported symbols. +2. If the imported symbol name is generated. +3. If importing symbols whose names are unclear by themselves, renaming can improve code clarity. + +### Exports + +Use named exports in all code: + +```ts +// Use named exports: +export class Foo { ... } +``` + +Do not use default exports. This ensures that all imports follow a uniform pattern. + +**Bad:** +```ts +// Do not use default exports: +export default class Foo { ... } // BAD! +``` + +> **Why?** Default exports provide no canonical name, which makes central maintenance difficult with relatively little benefit to code owners. Named exports have the benefit of erroring when import statements try to import something that hasn't been declared. + +#### Export visibility + +TypeScript does not support restricting the visibility for exported symbols. Only export symbols that are used outside of the module. Generally minimize the exported API surface of modules. + +#### Mutable exports + +Regardless of technical support, mutable exports can create hard to understand and debug code. One way to paraphrase this style point is that `export let` is not allowed. + +**Bad:** +```ts +export let foo = 3; +// In pure ES6, foo is mutable and importers will observe the value change after a second. +// In TS, if foo is re-exported by a second file, importers will not see the value change. +window.setTimeout(() => { + foo = 4; +}, 1000 /* ms */); +``` + +If one needs to support externally accessible and mutable bindings, they *should* instead use explicit getter functions. + +**Good:** +```ts +let foo = 3; +window.setTimeout(() => { + foo = 4; +}, 1000 /* ms */); +// Use an explicit getter to access the mutable export. +export function getFoo() { return foo; }; +``` + +#### Container classes + +Do not create container classes with static methods or properties for the sake of namespacing. + +**Bad:** +```ts +export class Container { + static FOO = 1; + static bar() { return 1; } +} +``` + +Instead, export individual constants and functions: + +**Good:** +```ts +export const FOO = 1; +export function bar() { return 1; } +``` + +### Import and export type + +#### Import type + +You may use `import type {...}` when you use the imported symbol only as a type. Use regular imports for values: + +```ts +import type {Foo} from './foo'; +import {Bar} from './foo'; + +import {type Foo, Bar} from './foo'; +``` + +> **Why?** The TypeScript compiler automatically handles the distinction and does not insert runtime loads for type references. This distinction is useful for compiler modes (`isolatedModules`) and build tools. + +#### Export type + +Use `export type` when re-exporting a type, e.g.: + +```ts +export type {AnInterface} from './foo'; +``` + +### Use modules not namespaces + +TypeScript supports two methods to organize code: *namespaces* and *modules*, but namespaces are disallowed. Your code *must* refer to code in other files using imports and exports. + +Your code *must not* use the `namespace Foo { ... }` construct. `namespace`s *may* only be used when required to interface with external, third party code. + +Code *must not* use `require` (as in `import x = require('...');`) for imports. Use ES6 module syntax. + +**Bad:** +```ts +// Bad: do not use namespaces: +namespace Rocket { + function launch() { ... } +} + +// Bad: do not use +/// + +// Bad: do not use require() +import x = require('mydep'); +``` + +## 4. Language features + +### Local variable declarations + +#### Use const and let + +Always use `const` or `let` to declare variables. Use `const` by default, unless a variable needs to be reassigned. Never use `var`. + +```ts +const foo = otherValue; // Use if "foo" never changes. +let bar = someValue; // Use if "bar" is ever assigned into later on. +``` + +**Bad:** +```ts +var foo = someValue; // Don't use - var scoping is complex and causes bugs. +``` + +#### One variable per declaration + +Every local variable declaration declares only one variable: declarations such as `let a = 1, b = 2;` are not used. + +### Array literals + +#### Do not use the `Array` constructor + +*Do not* use the `Array()` constructor, with or without `new`. It has confusing and contradictory usage. + +**Bad:** +```ts +const a = new Array(2); // [undefined, undefined] +const b = new Array(2, 3); // [2, 3]; +``` + +**Good:** +```ts +const a = [2]; +const b = [2, 3]; + +// Equivalent to Array(2): +const c = []; +c.length = 2; + +// [0, 0, 0, 0, 0] +Array.from({length: 5}).fill(0); +``` + +#### Do not define properties on arrays + +Do not define or use non-numeric properties on an array (other than `length`). Use a `Map` (or `Object`) instead. + +#### Using spread syntax + +Using spread syntax `[...foo];` is a convenient shorthand for shallow-copying or concatenating iterables. + +When using spread syntax, the value being spread *must* match what is being created. When creating an array, only spread iterables. Primitives (including `null` and `undefined`) *must not* be spread. + +#### Array destructuring + +Array literals may be used on the left-hand side of an assignment to perform destructuring. + +```ts +const [a, b, c, ...rest] = generateResults(); +let [, b,, d] = someArray; +``` + +Destructuring may also be used for function parameters. Always specify `[]` as the default value if a destructured array parameter is optional. + +**Good:** +```ts +function destructured([a = 4, b = 2] = []) { … } +``` + +**Disallowed:** +```ts +function badDestructuring([a, b] = [4, 2]) { … } +``` + +### Object literals + +#### Do not use the `Object` constructor + +The `Object` constructor is disallowed. Use an object literal (`{}` or `{a: 0, b: 1, c: 2}`) instead. + +#### Iterating objects + +Iterating objects with `for (... in ...)` is error prone. It will include enumerable properties from the prototype chain. Either filter values explicitly with an `if` statement, or use `for (... of Object.keys(...))`. + +**Bad:** +```ts +for (const x in someObj) { + // x could come from some parent prototype! +} +``` + +**Good:** +```ts +for (const x in someObj) { + if (!someObj.hasOwnProperty(x)) continue; + // now x was definitely defined on someObj +} +for (const x of Object.keys(someObj)) { // note: for _of_! + // now x was definitely defined on someObj +} +for (const [key, value] of Object.entries(someObj)) { // note: for _of_! + // now key was definitely defined on someObj +} +``` + +#### Using spread syntax + +Using spread syntax `[...foo]` is a convenient shorthand for creating a shallow copy of an object. When creating an object, only objects may be spread; arrays and primitives *must not* be spread. + +#### Computed property names + +Computed property names (e.g. `{['key' + foo()]: 42}`) are allowed. + +#### Object destructuring + +Object destructuring patterns may be used on the left-hand side of an assignment. + +Destructured objects may also be used as function parameters, but should be kept as simple as possible: a single level of unquoted shorthand properties. + +**Good:** +```ts +interface Options { + num?: number; + str?: string; +} + +function destructured({num, str = 'default'}: Options = {}) {} +``` + +**Disallowed:** +```ts +function nestedTooDeeply({x: {num, str}}: {x: Options}) {} +function nontrivialDefault({num, str}: Options = {num: 42, str: 'default'}) {} +``` + +### Classes + +#### Class declarations + +Class declarations *must not* be terminated with semicolons. + +**Good:** +```ts +class Foo { +} +``` + +**Bad:** +```ts +class Foo { +}; // Unnecessary semicolon +``` + +Statements that contain class expressions *must* be terminated with a semicolon. + +#### Class method declarations + +Class method declarations *must not* use a semicolon to separate individual method declarations. Method declarations should be separated from surrounding code by a single blank line. + +**Good:** +```ts +class Foo { + doThing() { + console.log("A"); + } + + getOtherThing(): number { + return 4; + } +} +``` + +##### Overriding toString + +The `toString` method may be overridden, but must always succeed and never have visible side effects. + +#### Static methods + +**Avoid private static methods:** Prefer module-local functions over private static methods. +**Do not rely on dynamic dispatch:** Code *should not* rely on dynamic dispatch of static methods. +**Avoid static `this` references:** Code *must not* use `this` in a static context. + +#### Constructors + +Constructor calls *must* use parentheses, even when no arguments are passed: `const x = new Foo();`. + +It is unnecessary to provide an empty constructor or one that simply delegates into its parent class because ES2015 provides a default class constructor. However constructors with parameter properties, visibility modifiers or parameter decorators *should not* be omitted. + +The constructor should be separated from surrounding code both above and below by a single blank line. + +#### Class members + +**No #private fields:** Do not use private fields (also known as private identifiers like `#ident`). Instead, use TypeScript's visibility annotations (`private ident`). +**Use readonly:** Mark properties that are never reassigned outside of the constructor with the `readonly` modifier. +**Parameter properties:** Use TypeScript parameter properties rather than plumbing an obvious initializer through to a class member. + +**Good:** +```ts +class Foo { + constructor(private readonly barService: BarService) {} +} +``` + +**Field initializers:** If a class member is not a parameter, initialize it where it's declared. + +**Properties used outside of class lexical scope:** Properties used from outside the lexical scope (e.g. Angular templates) *must not* use `private` visibility. Use `protected` or `public`. + +##### Getters and setters + +Getters and setters *may* be used. The getter method *must* be a pure function. + +##### Computed properties + +Computed properties may only be used in classes when the property is a symbol. + +#### Visibility + +Restricting visibility helps with keeping code decoupled. +* Limit symbol visibility as much as possible. +* TypeScript symbols are public by default. Never use the `public` modifier except when declaring non-readonly public parameter properties. + +#### Disallowed class patterns + +**Class prototypes:** Do not manipulate `prototype`s directly. + +### Functions + +#### Prefer function declarations for named functions + +Prefer function declarations over arrow functions or function expressions when defining named functions. + +**Good:** +```ts +function foo() { + return 42; +} +``` + +**Bad:** +```ts +const foo = () => 42; +``` + +#### Nested functions + +Functions nested within other methods or functions *may* use function declarations or arrow functions. In method bodies, arrow functions are preferred because they have access to the outer `this`. + +#### Do not use function expressions + +Do not use function expressions. Use arrow functions instead. + +#### Arrow function bodies + +Use arrow functions with concise bodies (expressions) or block bodies as appropriate. Only use a concise body if the return value of the function is actually used. + +**Good:** +```ts +// GOOD: return value is unused, use a block body. +myPromise.then(v => { + console.log(v); +}); +// GOOD: explicit `void` ensures no leaked return value +myPromise.then(v => void console.log(v)); +``` + +#### Rebinding `this` + +Function expressions and function declarations *must not* use `this` unless they specifically exist to rebind the `this` pointer. Prefer arrow functions. + +#### Prefer passing arrow functions as callbacks + +Callbacks can be invoked with unexpected arguments. Prefer passing an arrow-function that explicitly forwards parameters to the named callback. + +```ts +// GOOD: Arguments are explicitly passed to the callback +const numbers = ['11', '5', '3'].map((n) => parseInt(n)); +``` + +#### Arrow functions as properties + +Classes usually *should not* contain properties initialized to arrow functions. Code *should* always use arrow functions to call instance methods. + +#### Event handlers + +Event handlers *may* use arrow functions when there is no need to uninstall the handler. If the handler requires uninstallation, arrow function properties are the right approach. + +#### Parameter initializers + +Optional function parameters *may* be given a default initializer. Initializers *must not* have any observable side effects. + +#### Rest and spread + +Use a *rest* parameter instead of accessing `arguments`. Use function spread syntax instead of `Function.prototype.apply`. + +### this + +Only use `this` in class constructors and methods, functions that have an explicit `this` type declared, or in arrow functions defined in a scope where `this` may be used. Never use `this` to refer to the global object. + +### Primitive literals + +#### String literals + +**Use single quotes:** Ordinary string literals are delimited with single quotes (`'`), rather than double quotes (`"`). +**No line continuations:** Do not use line continuations (`\`) in string literals. +**Template literals:** Use template literals (delimited with `` ` ``) over complex string concatenation. + +#### Number literals + +Numbers may be specified in decimal, hex, octal, or binary. Use exactly `0x`, `0o`, and `0b` prefixes. Never include a leading zero unless it is immediately followed by `x`, `o`, or `b`. + +#### Type coercion + +TypeScript code *may* use the `String()` and `Boolean()` functions, string template literals, or `!!` to coerce types. + +Values of enum types *must not* be converted to booleans with `Boolean()` or `!!`, and must instead be compared explicitly. + +Code *must* use `Number()` to parse numeric values, and *must* check its return for `NaN` values explicitly. Code *must not* use unary plus (`+`) to coerce strings to numbers. Code *must not* use `parseInt` or `parseFloat` to parse numbers, except for non-base-10 strings. + +### Control structures + +#### Control flow statements and blocks + +Control flow statements (`if`, `else`, `for`, `do`, `while`, etc) always use braced blocks, even if the body contains only a single statement. + +**Exception:** `if` statements fitting on one line *may* elide the block. + +**Assignment in control statements:** Prefer to avoid assignment of variables inside control statements. + +#### Iterating containers + +Prefer `for (... of someArr)` to iterate over arrays. Do not use `for (... in ...)` to iterate over arrays. + +#### Grouping parentheses + +Optional grouping parentheses are omitted only when the author and reviewer agree that there is no reasonable chance that the code will be misinterpreted without them. + +#### Exception handling + +**Instantiate errors using `new`:** Always use `new Error()` when instantiating exceptions. +**Only throw errors:** Only throw (subclasses of) `Error`. Do not throw strings or raw objects. +**Catching and rethrowing:** When catching errors, code *should* assume that all thrown errors are instances of `Error`. +**Empty catch blocks:** It is very rarely correct to do nothing in response to a caught exception. Add a comment if it is intentional. + +#### Switch statements + +All `switch` statements *must* contain a `default` statement group. Non-empty statement groups *must not* fall through. + +#### Equality checks + +Always use triple equals (`===`) and not equals (`!==`). + +**Exception:** Comparisons to the literal `null` value *may* use the `==` and `!=` operators to cover both `null` and `undefined`. + +#### Type and non-nullability assertions + +Type assertions (`x as SomeType`) and non-nullability assertions (`y!`) are unsafe. You *should not* use them without an obvious or explicit reason. + +**Syntax:** Type assertions *must* use the `as` syntax (as opposed to ``). + +### Decorators + +Do not define new decorators. Only use the decorators defined by frameworks (Angular, Polymer). + +When using decorators, the decorator *must* immediately precede the symbol it decorates, with no empty lines between. + +### Disallowed features + +* **Wrapper objects:** TypeScript code *must not* instantiate the wrapper classes for the primitive types `String`, `Boolean`, and `Number`. +* **Automatic Semicolon Insertion:** Explicitly end all statements using a semicolon. +* **Const enums:** Code *must not* use `const enum`; use plain `enum` instead. +* **Debugger statements:** *Must not* be included in production code. +* **`with`:** Do not use the `with` keyword. +* **Dynamic code evaluation:** Do not use `eval` or the `Function(...string)` constructor. +* **Non-standard features:** Do not use non-standard ECMAScript or Web Platform features. +* **Modifying builtin objects:** Never modify builtin types. + +## 5. Naming + +### Identifiers + +Identifiers *must* use only ASCII letters, digits, underscores, and (rarely) the '$' sign. + +**Descriptive names:** Names *must* be descriptive and clear. Do not use abbreviations that are ambiguous. +**Camel case:** Treat abbreviations like acronyms as whole words (e.g., `loadHttpUrl`, not `loadHTTPURL`). + +### Rules by identifier type + +| Style | Category | +| :--- | :--- | +| `UpperCamelCase` | class / interface / type / enum / decorator / type parameters / component functions in TSX | +| `lowerCamelCase` | variable / parameter / function / method / property / module alias | +| `CONSTANT_CASE` | global constant values, including enum values | +| `#ident` | private identifiers are never used | + +**Type parameters:** Like in `Array`, *may* use a single upper case character (`T`) or `UpperCamelCase`. +**Private properties:** Do not use trailing or leading underscores for private properties. +**Constants:** `CONSTANT_CASE` indicates that a value is *intended* to not be changed. +**Imports:** Module namespace imports are `lowerCamelCase` (e.g. `import * as fooBar`). + +## 6. Type system + +### Type inference + +Code *may* rely on type inference. Leave out type annotations for trivially inferred types. Explicitly specifying types may be required to prevent generic type parameters from being inferred as `unknown`. + +### Undefined and null + +TypeScript supports `undefined` and `null` types. TypeScript code can use either. + +**Nullable/undefined type aliases:** Type aliases *must not* include `|null` or `|undefined` in a union type. Code *must* only add them when the alias is actually used. + +**Prefer optional over `|undefined`:** Use optional fields (`?`) rather than a `|undefined` type. + +### Use structural types + +Use interfaces to define structural types, not classes. + +### Prefer interfaces over type literal aliases + +When declaring types for objects, use interfaces instead of a type alias (`type Foo = {...}`) for the object literal expression. + +### `Array` Type + +For simple types, use the syntax sugar `T[]`. For anything more complex, use the longer form `Array`. + +### Indexable types + +In TypeScript, provide a meaningful label for the key in index signatures (e.g. `{[userName: string]: number}`). + +### Mapped and conditional types + +Always use the simplest type construct that can possibly express your code. Mapped & conditional types may be used, but avoid complexity. + +### `any` Type + +**Consider *not* to use `any`.** In circumstances where you want to use `any`, consider: +* Providing a more specific type (interfaces, inline types). +* Using `unknown`. +* Suppressing the lint warning and documenting why. + +### `unknown` over `any` + +Use `unknown` when a type is truly unknown. Narrow the type using a type guard to use it safely. + +### `{}​` Type + +Google3 code **should not** use `{}` for most use cases. Use `unknown`, `Record`, or `object` instead. + +### Tuple types + +Use tuple types `[string, string]` instead of creating specific "Pair" interfaces if appropriate. + +### Wrapper types + +Never use `String`, `Boolean`, `Number`, or `Object` types. Use `string`, `boolean`, `number`, `{}`, or `object`. + +## 7. Toolchain requirements + +### TypeScript compiler + +All TypeScript files must pass type checking using the standard tool chain. + +**`@ts-ignore`:** Do not use `@ts-ignore`, `@ts-expect-error`, or `@ts-nocheck`. You *may* use `@ts-expect-error` in unit tests, though generally *should not*. + +## 8. Comments and documentation + +### JSDoc versus comments + +* Use `/** JSDoc */` comments for documentation (users of the code). +* Use `// line comments` for implementation comments. + +### JSDoc general form + +```ts +/** + * Multiple lines of JSDoc text are written here, + * wrapped normally. + * @param arg A number to do something to. + */ +function doSomething(arg: number) { … } +``` + +### JSDoc tags + +Most tags must occupy their own line. + +### Document all top-level exports of modules + +Use `/** JSDoc */` comments to communicate information to the users of your code. + +### Class comments + +JSDoc comments for classes should provide the reader with enough information to know how and when to use the class. + +### Parameter property comments + +To document parameter properties (e.g. `constructor(private readonly foo: Foo)`), use JSDoc's `@param` annotation on the constructor. + +### JSDoc type annotations + +JSDoc type annotations are redundant in TypeScript. Do not declare types in `@param` or `@return` blocks. + +## 9. Policies + +### Consistency + +For any style question that isn't settled definitively by this specification, do what the other code in the same file is already doing ("be consistent"). + +### Deprecation + +Mark deprecated methods, classes or interfaces with an `@deprecated` JSDoc annotation. \ No newline at end of file diff --git a/packages/browseros-agent/.config/README.md b/packages/browseros-agent/.config/README.md new file mode 100644 index 000000000..eab9064c1 --- /dev/null +++ b/packages/browseros-agent/.config/README.md @@ -0,0 +1,33 @@ +# Worktrunk Setup + +This repo uses [Worktrunk](https://github.com/max-sixty/worktrunk) for running multiple Claude Code agents in parallel on different branches. + +## Install Worktrunk + +```bash +brew install max-sixty/worktrunk/wt +wt config shell install +# restart terminal +``` + +## Quick Commands + +| Task | Command | +|------|---------| +| Create worktree + start Claude | `wt switch -c -x claude feat-name` | +| Switch to existing worktree | `wt switch feat-name` | +| List all worktrees | `wt list` | +| Create PR | `gh pr create` | +| Remove worktree | `wt remove feat-name` | + +## What happens on `wt switch -c` + +1. Creates new worktree at `../browseros-server.feat-name/` +2. Runs `bun install` +3. Copies `.env.development` files from main worktree + +## Hooks + +Hooks are configured in `.config/wt.toml`: + +- **post-create**: Runs `bun install` and copies env files from the main worktree diff --git a/packages/browseros-agent/.config/wt.toml b/packages/browseros-agent/.config/wt.toml new file mode 100644 index 000000000..3a57a9603 --- /dev/null +++ b/packages/browseros-agent/.config/wt.toml @@ -0,0 +1,9 @@ +[post-create] +install = "bun install" +# env-server = "cp {{ repo_root }}/apps/server/.env.development apps/server/.env.development" +# env-agent = "cp {{ repo_root }}/apps/agent/.env.development apps/agent/.env.development" +env = "for f in {{ repo_root }}/apps/*/.env.*; do [ -f \"$f\" ] && cp \"$f\" \"${f#{{ repo_root }}/}\"; done 2>/dev/null || true" +llm = "cp -r {{ repo_root }}/.llm . 2>/dev/null || true" + +[pre-remove] +llm-sync = "rsync -au .llm/ {{ repo_root }}/.llm/ 2>/dev/null || true" diff --git a/packages/browseros-agent/.gitattributes b/packages/browseros-agent/.gitattributes new file mode 100644 index 000000000..5d287df87 --- /dev/null +++ b/packages/browseros-agent/.gitattributes @@ -0,0 +1,2 @@ +third_party/bin/* filter=lfs diff=lfs merge=lfs -text +third_party/bin/rcedit-x64.exe filter=lfs diff=lfs merge=lfs -text diff --git a/packages/browseros-agent/.github/dependabot.yml b/packages/browseros-agent/.github/dependabot.yml new file mode 100644 index 000000000..06f37ffe4 --- /dev/null +++ b/packages/browseros-agent/.github/dependabot.yml @@ -0,0 +1,41 @@ +version: 2 +updates: + - package-ecosystem: bun + directory: / + schedule: + interval: weekly + day: 'sunday' + time: '02:00' + timezone: Europe/Berlin + open-pull-requests-limit: 10 + groups: + dependencies: + applies-to: security-updates + dependency-type: production + exclude-patterns: + - 'puppeteer*' + patterns: + - '*' + dev-dependencies: + applies-to: security-updates + dependency-type: development + exclude-patterns: + - 'puppeteer*' + patterns: + - '*' + puppeteer: + patterns: + - 'puppeteer*' + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: 'sunday' + time: '04:00' + timezone: Europe/Berlin + open-pull-requests-limit: 10 + groups: + all: + applies-to: security-updates + patterns: + - '*' diff --git a/packages/browseros-agent/.github/workflows/audit.yml b/packages/browseros-agent/.github/workflows/audit.yml new file mode 100644 index 000000000..d404d8f2f --- /dev/null +++ b/packages/browseros-agent/.github/workflows/audit.yml @@ -0,0 +1,189 @@ +name: Daily Security Audit + +on: + schedule: + # Runs at midnight IST (6:30 PM UTC previous day) + - cron: "30 18 * * *" + workflow_dispatch: # Allows manual triggering + +jobs: + security-audit: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun ci + + - name: Run security audit + id: audit + continue-on-error: true + run: | + # Run audit and capture output (skip the version line) + bun audit --json 2>&1 | tail -n 1 > audit-results.json || true + + # Check if vulnerabilities exist + VULN_COUNT=$(cat audit-results.json | bun -e "const data = JSON.parse(require('fs').readFileSync(0, 'utf-8')); console.log(Object.keys(data).reduce((sum, pkg) => sum + data[pkg].length, 0))") + echo "vuln_count=$VULN_COUNT" >> $GITHUB_OUTPUT + + - name: Parse audit results + id: parse + if: always() + run: | + cat > parse-audit.ts << 'EOF' + const fs = require('fs'); + const auditData = JSON.parse(fs.readFileSync('audit-results.json', 'utf-8')); + + // Collect all vulnerabilities from all packages + const allVulns: any[] = []; + let totalCount = 0; + + for (const [packageName, vulns] of Object.entries(auditData)) { + if (Array.isArray(vulns)) { + vulns.forEach((vuln: any) => { + allVulns.push({ ...vuln, packageName }); + totalCount++; + }); + } + } + + if (totalCount === 0) { + console.log(JSON.stringify({ + text: "✅ *Daily Security Audit - No Vulnerabilities Found*", + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: "✅ *Daily Security Audit*\n\nNo vulnerabilities found in dependencies!" + } + }, + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `Repository: ${process.env.GITHUB_REPOSITORY} | Branch: ${process.env.GITHUB_REF_NAME}` + } + ] + } + ] + })); + process.exit(0); + } + + // Count by severity + const severityCounts = { + critical: 0, + high: 0, + moderate: 0, + low: 0 + }; + + allVulns.forEach(vuln => { + severityCounts[vuln.severity as keyof typeof severityCounts]++; + }); + + let message = `⚠️ *Daily Security Audit - ${totalCount} Vulnerabilit${totalCount === 1 ? 'y' : 'ies'} Found*\n\n`; + message += `*Severity Breakdown:*\n`; + message += `• Critical: ${severityCounts.critical}\n`; + message += `• High: ${severityCounts.high}\n`; + message += `• Moderate: ${severityCounts.moderate}\n`; + message += `• Low: ${severityCounts.low}\n\n`; + + message += `*Top Vulnerabilities:*\n`; + + // Sort by severity + const severityOrder = { critical: 0, high: 1, moderate: 2, low: 3 }; + allVulns.sort((a, b) => + severityOrder[a.severity as keyof typeof severityOrder] - + severityOrder[b.severity as keyof typeof severityOrder] + ); + + allVulns.slice(0, 5).forEach(vuln => { + const emoji = { + critical: '🔴', + high: '🟠', + moderate: '🟡', + low: '🟢' + }[vuln.severity] || '⚪'; + + message += `\n${emoji} *${vuln.title}*\n`; + message += ` Package: \`${vuln.packageName}\`\n`; + message += ` Severity: ${vuln.severity.toUpperCase()}\n`; + message += ` Vulnerable: ${vuln.vulnerable_versions}\n`; + if (vuln.cwe?.length) { + message += ` CWE: ${vuln.cwe.join(', ')}\n`; + } + if (vuln.cvss?.score) { + message += ` CVSS: ${vuln.cvss.score}\n`; + } + if (vuln.url) { + message += ` <${vuln.url}|View Details>\n`; + } + }); + + if (allVulns.length > 5) { + message += `\n_...and ${allVulns.length - 5} more vulnerabilit${allVulns.length - 5 === 1 ? 'y' : 'ies'}_`; + } + + const payload = { + text: `⚠️ Security Audit: ${totalCount} vulnerabilit${totalCount === 1 ? 'y' : 'ies'} found`, + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: message + } + }, + { + type: "actions", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "View Full Report" + }, + url: `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}` + } + ] + }, + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `Repository: ${process.env.GITHUB_REPOSITORY} | Branch: ${process.env.GITHUB_REF_NAME}` + } + ] + } + ] + }; + + console.log(JSON.stringify(payload)); + EOF + + bun run parse-audit.ts > slack-payload.json + + - name: Send to Slack + if: always() + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + run: | + curl -X POST \ + -H 'Content-Type: application/json' \ + -d @slack-payload.json \ + $SLACK_WEBHOOK_URL + + - name: Fail if vulnerabilities found + if: steps.audit.outputs.vuln_count != '0' + run: | + echo "Security audit found vulnerabilities" + exit 1 diff --git a/packages/browseros-agent/.github/workflows/branch-cleaner.yml b/packages/browseros-agent/.github/workflows/branch-cleaner.yml new file mode 100644 index 000000000..bc40e73f6 --- /dev/null +++ b/packages/browseros-agent/.github/workflows/branch-cleaner.yml @@ -0,0 +1,17 @@ +name: GitHub Branch Cleaner + +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + cleanup: + name: Clean up merged branches + runs-on: ubuntu-latest + steps: + - uses: mmorenoregalado/action-branches-cleaner@v2.0.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + base_branches: main + days_old_threshold: 30 diff --git a/packages/browseros-agent/.github/workflows/cla.yml b/packages/browseros-agent/.github/workflows/cla.yml new file mode 100644 index 000000000..d92ce0cda --- /dev/null +++ b/packages/browseros-agent/.github/workflows/cla.yml @@ -0,0 +1,58 @@ +name: CLA Assistant + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +permissions: + actions: write + contents: write + pull-requests: write + statuses: write + +jobs: + cla: + runs-on: ubuntu-latest + if: | + (github.event_name == 'pull_request_target') || + (github.event_name == 'issue_comment' && github.event.issue.pull_request && + (github.event.comment.body == 'recheck' || + github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA')) + steps: + - name: CLA Assistant + uses: contributor-assistant/github-action@v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGNATURES_TOKEN }} + with: + path-to-signatures: 'cla-signatures.json' + path-to-document: 'https://github.com/${{ github.repository }}/blob/main/CLA.md' + branch: 'main' + remote-organization-name: 'browseros-ai' + remote-repository-name: 'cla-signatures' + allowlist: 'bot*,*[bot],dependabot,renovate,github-actions,snyk-bot,imgbot,greenkeeper,semantic-release-bot,allcontributors' + lock-pullrequest-aftermerge: false + custom-notsigned-prcomment: | + Thank you for your contribution! Before we can merge this PR, we need you to sign our [Contributor License Agreement](https://github.com/${{ github.repository }}/blob/main/CLA.md). + + **To sign the CLA**, please add a comment to this PR with the following text: + + ``` + I have read the CLA Document and I hereby sign the CLA + ``` + + You only need to sign once. After signing, this check will pass automatically. + + --- +
+ Troubleshooting + + - **Already signed but still failing?** Comment `recheck` to trigger a re-verification. + - **Signed with a different email?** Make sure your commit email matches your GitHub account email, or add your commit email to your GitHub account. + +
+ custom-pr-sign-comment: 'I have read the CLA Document and I hereby sign the CLA' + custom-allsigned-prcomment: | + All contributors have signed the CLA. Thank you! diff --git a/packages/browseros-agent/.github/workflows/claude.yml b/packages/browseros-agent/.github/workflows/claude.yml new file mode 100644 index 000000000..60997d57b --- /dev/null +++ b/packages/browseros-agent/.github/workflows/claude.yml @@ -0,0 +1,48 @@ +name: Claude Code + +on: + pull_request: + types: [opened, ready_for_review] + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + github.event_name == 'pull_request' || + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: write # Can push branches and create commits + pull-requests: write # Can create and update PRs + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Allow all tools - branch protection rules at repo level prevent direct pushes to main/master + # Omitting --allowedTools means all tools are available by default + diff --git a/packages/browseros-agent/.github/workflows/code-quality.yml b/packages/browseros-agent/.github/workflows/code-quality.yml new file mode 100644 index 000000000..8bcf870ed --- /dev/null +++ b/packages/browseros-agent/.github/workflows/code-quality.yml @@ -0,0 +1,49 @@ +name: Code Quality + +on: + pull_request: + branches: + - main + +jobs: + biome: + name: runner / Biome + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Setup Biome + uses: biomejs/setup-biome@v2 + with: + version: latest + + - name: Run Biome + run: biome ci . + + typecheck: + name: runner / Typecheck + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun ci + + - name: Run codegen + run: bun run --cwd apps/agent codegen + + - name: Run Typecheck + run: bun run typecheck diff --git a/packages/browseros-agent/.github/workflows/pr-title.yml b/packages/browseros-agent/.github/workflows/pr-title.yml new file mode 100644 index 000000000..19cd1fcdd --- /dev/null +++ b/packages/browseros-agent/.github/workflows/pr-title.yml @@ -0,0 +1,20 @@ +name: PR Conventional Commit Validation + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +permissions: + pull-requests: write # Read PR details and add labels + issues: write # Labels are managed via issues API + contents: read # Read repository content + +jobs: + validate-pr-title: + runs-on: ubuntu-latest + steps: + - name: PR Conventional Commit Validation + uses: ytanikin/pr-conventional-commits@1.5.1 + with: + task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert","build"]' + custom_labels: '{"feat": "feature", "fix": "fix", "docs": "documentation", "test": "test", "ci": "CI/CD", "refactor": "refactor", "perf": "performance", "chore": "chore", "revert": "revert", "wip": "WIP"}' diff --git a/packages/browseros-agent/.github/workflows/release-agent-sdk.yml b/packages/browseros-agent/.github/workflows/release-agent-sdk.yml new file mode 100644 index 000000000..36b3b1f80 --- /dev/null +++ b/packages/browseros-agent/.github/workflows/release-agent-sdk.yml @@ -0,0 +1,37 @@ +name: Release Agent SDK + +on: + workflow_dispatch: + +jobs: + publish: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/agent-sdk + + steps: + - uses: actions/checkout@v6 + + - uses: oven-sh/setup-bun@v2 + + - uses: actions/setup-node@v6 + with: + node-version: "20" + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + run: bun ci + working-directory: . + + - name: Build + run: bun run build + + - name: Test + run: bun test + + - name: Publish + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/packages/browseros-agent/.github/workflows/test.yml b/packages/browseros-agent/.github/workflows/test.yml new file mode 100644 index 000000000..b5266643d --- /dev/null +++ b/packages/browseros-agent/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Tests + +on: [] + +jobs: + test: + name: Run Tests + runs-on: macos-latest + timeout-minutes: 10 + + steps: + - name: 📥 Checkout code + uses: actions/checkout@v6 + + - name: 🧰 Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: 📦 Install dependencies + run: bun ci + + - name: 🧪 Run all tests + run: bun test:all + env: + PUPPETEER_EXECUTABLE_PATH: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome diff --git a/packages/browseros-agent/.gitignore b/packages/browseros-agent/.gitignore new file mode 100644 index 000000000..da255e579 --- /dev/null +++ b/packages/browseros-agent/.gitignore @@ -0,0 +1,193 @@ +# Logs +logs +*.log +*.log.old +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* +.env.dev +.env.prod + +# Ignore all .env files except .env.example +**/.env.* +!**/.env.example +!**/.env.production.example + + +# sqlite database +browseros.db +browseros.db-* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# Claude +.claude/* +!.claude/skills/ +!.claude/commands/ +.worktrees/ + +# Build unpublished docs +# docs/ + +# TypeScript cache +*.tsbuildinfo + +# TypeScript compiled output in tests (source files are .ts) +**/tests/**/*.js +**/tests/**/*.d.ts +**/tests/**/*.d.ts.map + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +packages/*/dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Stores VSCode specific settings +.vscode +!.vscode/*.template.json +!.vscode/extensions.json + +# Stores Zed specific settings +.zed + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Build output directory +/build/ + +# Compiled binaries +browseros-server +browseros-server.exe +browseros-server-* +tools/dev/browseros-dev + +log.txt + +.DS_Store + +# Testing iteration temp files +tmp/ + +# Coding agent artifacts +.agent/ +.llm/ +.grove/ diff --git a/packages/browseros-agent/.npmrc b/packages/browseros-agent/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/packages/browseros-agent/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/packages/browseros-agent/.vscode/launch.json b/packages/browseros-agent/.vscode/launch.json new file mode 100644 index 000000000..9a337ac76 --- /dev/null +++ b/packages/browseros-agent/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "bun", + "internalConsoleOptions": "openOnSessionStart", + "request": "launch", + "name": "Debug BrowserOS Server", + "program": "src/index.ts", + "cwd": "${workspaceFolder}/apps/server", + "stopOnEntry": false, + "watchMode": false, + "env": { + "BUN_ENV_FILE": ".env.development" + } + } + ] +} diff --git a/packages/browseros-agent/CLA.md b/packages/browseros-agent/CLA.md new file mode 100644 index 000000000..3c45d52ac --- /dev/null +++ b/packages/browseros-agent/CLA.md @@ -0,0 +1,50 @@ +# Contributor License Agreement + +Thank you for your interest in contributing to BrowserOS Server. + +This Contributor License Agreement ("Agreement") documents the rights granted by contributors to this project. This is a legally binding document, so please read it carefully before agreeing. + +## 1. Definitions + +"You" (or "Your") means the individual who submits a Contribution to this project. + +"Contribution" means any original work of authorship, including any modifications or additions to an existing work, that you submit to this project. + +"Project" means BrowserOS Server and related repositories maintained under the same organization. + +## 2. Grant of Copyright License + +You hereby grant to the Project maintainers and to recipients of software distributed by the Project a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute your Contributions and such derivative works. + +## 3. Grant of Patent License + +You hereby grant to the Project maintainers and to recipients of software distributed by the Project a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Contribution, where such license applies only to those patent claims licensable by you that are necessarily infringed by your Contribution alone or by combination of your Contribution with the Project. + +## 4. Your Representations + +You represent that: + +- You are legally entitled to grant the above licenses. +- Each of your Contributions is your original creation. +- Your Contribution does not violate any third party's copyrights, trademarks, patents, or other intellectual property rights. +- If your employer has rights to intellectual property that you create, you have received permission to make the Contribution on behalf of that employer, or your employer has waived such rights for your Contribution. + +## 5. No Warranty + +You provide your Contributions on an "AS IS" basis, without warranties or conditions of any kind, either express or implied, including without limitation any warranties or conditions of title, non-infringement, merchantability, or fitness for a particular purpose. + +## 6. Agreement + +By signing this Agreement, you accept and agree to the terms and conditions of this Contributor License Agreement for your present and future Contributions submitted to the Project. + +--- + +## How to Sign + +To sign this CLA, add a comment to your pull request with the following text: + +``` +I have read the CLA Document and I hereby sign the CLA +``` + +You only need to sign once. After signing, all your future contributions to this project will be covered. diff --git a/packages/browseros-agent/CLAUDE.md b/packages/browseros-agent/CLAUDE.md new file mode 100644 index 000000000..5cbbc9306 --- /dev/null +++ b/packages/browseros-agent/CLAUDE.md @@ -0,0 +1,167 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Coding guidelines + +- **Use extensionless imports.** Do not use `.js` extensions in TypeScript imports. Bun resolves `.ts` files automatically. + ```typescript + // ✅ Correct + import { foo } from './utils' + import type { Bar } from '../types' + + // ❌ Wrong + import { foo } from './utils.js' + ``` +- Write minimal code comments. Only add comments for non-obvious logic, complex algorithms, or critical warnings. Skip comments for self-explanatory code, obvious function names, and simple operations. +- Logger messages should not include `[prefix]` tags (e.g., `[Config]`, `[HTTP Server]`). Source tracking automatically adds file:line:function in development mode. +- Avoid magic constants scattered in the codebase. Use `@browseros/shared` for all shared configuration: + - `@browseros/shared/constants/ports` - Port numbers (DEFAULT_PORTS, TEST_PORTS) + - `@browseros/shared/constants/timeouts` - Timeout values (TIMEOUTS) + - `@browseros/shared/constants/limits` - Rate limits, pagination, content limits (RATE_LIMITS, AGENT_LIMITS, etc.) + - `@browseros/shared/constants/urls` - External service URLs (EXTERNAL_URLS) + - `@browseros/shared/constants/paths` - File system paths (PATHS) + - `@browseros/shared/types/logger` - Logger interface types (LoggerInterface, LogLevel) + +## File Naming Convention + +Use **kebab-case** for all file and folder names: + +| Type | Convention | Example | +|------|------------|---------| +| Multi-word files | kebab-case | `gemini-agent.ts`, `mcp-context.ts` | +| Single-word files | lowercase | `types.ts`, `browser.ts`, `index.ts` | +| Test files | `.test.ts` suffix | `mcp-context.test.ts` | +| Folders | kebab-case | `controller-server/`, `rate-limiter/` | + +Classes remain PascalCase in code, but live in kebab-case files: +```typescript +// file: gemini-agent.ts +export class GeminiAgent { ... } +``` + +## Project Overview + +**BrowserOS Server** - The automation engine inside BrowserOS. This MCP server powers the built-in AI agent and lets external tools like `claude-code` or `gemini-cli` control the browser. Starts automatically when BrowserOS launches. + +## Bun Preferences + +Default to using Bun instead of Node.js: + +- Use `bun ` instead of `node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun install` instead of `npm install` +- Use `bun run + + +
+ + + diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx new file mode 100644 index 000000000..a701d14f7 --- /dev/null +++ b/packages/browseros-agent/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx @@ -0,0 +1,182 @@ +import { Loader2, Send, Square } from 'lucide-react' +import { type FC, type FormEvent, useEffect, useRef, useState } from 'react' +import { MessageResponse } from '@/components/ai-elements/message' +import { Button } from '@/components/ui/button' +import { Textarea } from '@/components/ui/textarea' +import { cn } from '@/lib/utils' +import type { Message } from './useSurveyChat' +import { useVoiceInput } from './useVoiceInput' +import { VoiceInputButton } from './VoiceInputButton' + +interface Props { + messages: Message[] + isStreaming: boolean + onSendMessage: (text: string) => void + onStop: () => void +} + +const MessageBubble: FC<{ message: Message }> = ({ message }) => { + const isUser = message.role === 'user' + + return ( +
+
+ {message.content ? ( + {message.content} + ) : ( + + + Thinking... + + )} +
+
+ ) +} + +const WAVEFORM_BARS = [0, 1, 2, 3, 4] as const + +const WaveformIndicator: FC<{ level: number }> = ({ level }) => { + return ( +
+ {WAVEFORM_BARS.map((barIndex) => { + const barLevel = Math.max( + 0.2, + Math.sin((barIndex / WAVEFORM_BARS.length) * Math.PI) * (level / 100), + ) + return ( +
+ ) + })} +
+ ) +} + +export const Chat: FC = ({ + messages, + isStreaming, + onSendMessage, + onStop, +}) => { + const [input, setInput] = useState('') + const messagesEndRef = useRef(null) + + const voice = useVoiceInput() + + const messagesLength = messages.length + // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally scroll on message count change + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messagesLength]) + + // Insert transcript into input when transcription completes + useEffect(() => { + if (voice.transcript && !voice.isTranscribing) { + setInput((prev) => { + const separator = prev.trim() ? ' ' : '' + return prev + separator + voice.transcript + }) + voice.clearTranscript() + } + }, [voice]) + + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + if (!input.trim() || isStreaming || voice.isRecording) return + onSendMessage(input.trim()) + setInput('') + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSubmit(e) + } + } + + const isInputDisabled = + isStreaming || voice.isRecording || voice.isTranscribing + + return ( +
+
+ {messages.map((msg) => ( + + ))} +
+
+ +
+ {voice.error && ( +
{voice.error}
+ )} + +
+ {voice.isRecording ? ( +
+ + + Listening... + +
+ ) : ( +