Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The local Supabase stack still uses shared default ports and container names. In
## System map

- `src/` is the React 18 and TypeScript client. Routes and lazy page entry points live in `src/App.tsx`.
- `src/lib/stores/` owns browser state and Supabase hydration. `src/lib/ai/client.ts` is the client AI boundary.
- `src/lib/stores/` owns browser state and Supabase hydration. `src/lib/ai/client.ts` is the client AI boundary: app code imports only from it. The request transport is `request.ts` (streaming Ask uses `streaming.ts`), and each feature has a file in `features/`, mirroring `supabase/functions/ai/features/`.
- `packages/core/` (`@debatelab/core`, npm workspace) holds platform-neutral shared code: Zod schemas, validation, languages, Crossfire and Sparring logic. No DOM globals, React, or `import.meta` there; old `src/lib/` paths re-export it. Stores and i18n catalogs stay in `src/`.
- `supabase/functions/` contains Deno Edge Functions. Shared auth, CORS, research, prompts, cost, and validation code lives in `supabase/functions/_shared/`.
- `supabase/migrations/` is the database history and schema source of truth.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Client-side environment selection is a developer convenience, not authorization.

## AI request flow

[`src/lib/ai/client.ts`](../src/lib/ai/client.ts) is the browser AI boundary.
[`src/lib/ai/client.ts`](../src/lib/ai/client.ts) is the browser AI boundary: app code imports only from it. The request transport is `request.ts` (streaming Ask uses `streaming.ts`), and each feature has a file in `features/`, mirroring `supabase/functions/ai/features/`.

1. A feature page validates input and calls a feature wrapper.
2. The client reads the current Supabase session.
Expand Down
2 changes: 1 addition & 1 deletion docs/standards/boundaries.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Trust boundaries and validation

Part of the [coding standards](../../CODING_STANDARDS.md). The client AI boundary is [`src/lib/ai/client.ts`](../../src/lib/ai/client.ts); the server boundaries are the Edge Function handlers described in [supabase/README.md](../../supabase/README.md).
Part of the [coding standards](../../CODING_STANDARDS.md). The client AI boundary is [`src/lib/ai/client.ts`](../../src/lib/ai/client.ts), and response validation lives in `src/lib/ai/request.ts`, `src/lib/ai/streaming.ts`, `src/lib/ai/sources.ts`, and each `src/lib/ai/features/*.ts`; the server boundaries are the Edge Function handlers described in [supabase/README.md](../../supabase/README.md).

## Validate at the boundary, then trust the type

Expand Down
2 changes: 1 addition & 1 deletion e2e/fixtures/ai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const CORS_HEADERS = {
};

// The client rejects a generate response whose contention count does not match
// the requested length (see expectedContentionCount in src/lib/ai/client.ts).
// the requested length (see expectedContentionCount in src/lib/ai/features/generate.ts).
const CONTENTION_COUNT: Record<string, number> = { brief: 2, standard: 3, comprehensive: 4 };

const CONTENTIONS = [
Expand Down
26 changes: 26 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

// The AI core and feature files behind src/lib/ai/client.ts, by alias or
// relative path, with or without an extension. The server's same-named
// modules under supabase/functions/ai/ stay importable.
const aiInternalImport = String.raw`^(?!.*supabase/functions/)(.*/)?ai/(request|errors|sources|text|features(/.*)?)(\.[cm]?[jt]sx?)?/?$`;
const aiInternalMessage = "Import AI requests and errors from @/lib/ai/client.";

export default tseslint.config(
{ ignores: ["dist", "coverage", ".remember", "packages/**/node_modules", "packages/**/dist"] },
{
Expand Down Expand Up @@ -80,6 +86,26 @@ export default tseslint.config(
],
},
},
{
// App code imports AI requests and errors through src/lib/ai/client.ts
// only. The core and feature files behind it are internal.
files: ["src/**/*.{ts,tsx}", "e2e/**/*.ts", "agentic/**/*.ts"],
ignores: ["src/lib/ai/**"],
rules: {
"no-restricted-imports": [
"error",
{ patterns: [{ regex: aiInternalImport, message: aiInternalMessage }] },
],
"no-restricted-syntax": [
"error",
{
// esquery ends a regex at "/", so the pattern spells it as \x2F.
selector: `ImportExpression[source.value=/${aiInternalImport.replaceAll("/", "\\x2F")}/]`,
message: aiInternalMessage,
},
],
},
},
{
// Fixed sleeps pass or fail with machine speed. Wait for a condition.
files: ["e2e/**/*.ts", "agentic/**/*.ts", ".claude/skills/verify-debatelab/**/*.ts"],
Expand Down
Loading
Loading