diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4610a184d..15dad1e85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: # whichever vitest worker hit it first and cascading into "0 test" # failures across the rest of that worker's queue. Node 20 was # already below what a fresh dependency needs; bump to match. - node-version: '22' + node-version: '22.19.0' cache: 'npm' - run: npm ci - run: npx tsc --noEmit @@ -34,6 +34,9 @@ jobs: # which requires ITS dist/index.js to exist too - build it before the # test step (this is a separate package from the root SDK above). - run: npm run build --workspace=packages/create-loushy-agent + # Every ```ts block in README.md and docs/*.md must type-check against + # src/ (quick-start also runs against the packed tarball). Needs dist/. + - run: npm run docs:verify-snippets -- --skip-build - run: npm run test:coverage # Agent Forge (apps/agent-forge) imports the SDK as # `@loushy/build-ai-agent` via `file:../..`, i.e. the root dist/ built diff --git a/.gitignore b/.gitignore index 05d40d7ae..744769f77 100644 --- a/.gitignore +++ b/.gitignore @@ -53,5 +53,7 @@ docs/api/ # fallow cache/snapshots .fallow/ +# docs snippet verifier temp dirs +.docs-snippets-*/ # Local agent worktrees and session state .claude/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac37396cf..2c2867ca0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ By participating in this project, you agree to maintain a respectful and inclusi ### Prerequisites -- Node.js >= 18.0.0 +- Node.js >= 22.19.0 - npm, pnpm, or yarn ### Setup diff --git a/README.md b/README.md index 6b6225e78..1bbc439bb 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ const paused = await AgentExecutor.execute({ const result = await resumeAfterApproval( { id: paused.approvalId!, approved: true }, approvalStore, - provider, registry, + provider, ); ``` @@ -240,16 +240,29 @@ registry.register('send_email', { Flows orchestrate multi-step workflows within a single agent: ```typescript -import { FlowBuilder, FlowExecutor } from '@loushy/build-ai-agent'; +import { FlowBuilder, FlowExecutor, type EditorStep } from '@loushy/build-ai-agent'; + +// FlowBuilder is a metadata builder: setCode/setName/setInputs/setFlow(...).build(). +// The executable node types ('sequence', 'llmCall', 'oneOf', 'setVariable', ...) are +// those handled by FlowExecutor; EditorStep currently types the editor-side shapes, +// hence the cast. +const steps = { + type: 'sequence', + steps: [ + { type: 'llmCall', prompt: 'Classify this message as billing, technical or sales: {{message}}', outputVariable: 'category' }, + { type: 'llmCall', prompt: 'Write a one-line reply for a {{category}} request: {{message}}' }, + ], +} as unknown as EditorStep; const flow = new FlowBuilder() - .addNode({ id: 'start', type: 'llm', data: { prompt: 'Analyze user input' } }) - .addNode({ id: 'decide', type: 'conditional', data: { condition: 'output.sentiment === "positive"' } }) - .addEdge('start', 'decide') + .setCode('triage') + .setName('Triage') + .addInput({ name: 'message', type: 'shortText', required: true }) + .setFlow(steps) .build(); // FlowExecutor is a static API too: execute(flow, context, onEvent?) -const result = await FlowExecutor.execute(flow, { agent, provider, variables: {} }); +const result = await FlowExecutor.execute(flow, { agent, provider, variables: { message: input } }); ``` ### LLM Providers @@ -298,8 +311,8 @@ const paused = await AgentExecutor.execute({ const result = await resumeAfterApproval( { id: paused.approvalId!, approved: true }, approvalStore, - provider, toolRegistry, + provider, ); ``` @@ -386,9 +399,9 @@ if (!verdict.pass) { ### Tracing & observability ```typescript -import { AgentExecutor, withSpan } from '@loushy/build-ai-agent'; +import { AgentExecutor, withSpan, type TraceExporter } from '@loushy/build-ai-agent'; -const exporter = { +const exporter: TraceExporter = { onSpanStart: (span) => console.log('[start]', span.name, span.attributes), onSpanEnd: (span) => console.log('[end]', span.name, span.endTime! - span.startTime, 'ms'), }; @@ -423,8 +436,8 @@ defineEval({ ### MCP tools & sandboxing ```typescript -import { loadMcpTools } from '@loushy/build-ai-agent/tools/mcp/McpToolLoader'; -import { SubprocessSandbox } from '@loushy/build-ai-agent'; +import { loadMcpTools } from '@loushy/build-ai-agent/mcp'; +import { AgentExecutor, SubprocessSandbox } from '@loushy/build-ai-agent'; // Turn any MCP server's tools into ToolDescriptors, namespaced __ const linearTools = await loadMcpTools(mcpClient, 'linear'); @@ -450,12 +463,13 @@ const hash = await sha256('password', 'salt'); ### Storage ```typescript -import { StorageService } from '@loushy/build-ai-agent'; +import { StorageService, type FileSystemAdapter } from '@loushy/build-ai-agent'; import fs from 'node:fs'; import path from 'node:path'; -// fs/path are injected as typed adapters (LOU-A7) rather than `any` -const storage = new StorageService('user-123', 'attachments', fs, path); +// fs/path are injected as adapters (LOU-A7). Node's `fs` is structurally close +// but its writeFileSync options type is wider, so it needs a cast. +const storage = new StorageService('user-123', 'attachments', fs as unknown as FileSystemAdapter, path); await storage.saveAttachment(file, 'document.pdf'); const buffer = storage.readAttachment('document.pdf'); diff --git a/docs/configuration.md b/docs/configuration.md index fc22977fb..73b1b86d9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -48,6 +48,7 @@ its tools with `loadMcpTools()`, then pass the result to `createAgent()` (or register it on a `ToolRegistry`): ```ts +import { createAgent } from '@loushy/build-ai-agent'; import { loadMcpTools } from '@loushy/build-ai-agent/mcp'; const tools = await loadMcpTools(mcpClient, 'my-server'); diff --git a/docs/eslint-baseline-followup.md b/docs/eslint-baseline-followup.md index f977341b8..94f0559d9 100644 --- a/docs/eslint-baseline-followup.md +++ b/docs/eslint-baseline-followup.md @@ -1,277 +1,36 @@ # ESLint Baseline Follow-up -This file tracks pre-existing lint violations surfaced by the ESLint flat config introduced in LOU-B1. These were present at baseline and were intentionally left unfixed as out of scope for LOU-B1. Fix incrementally in follow-up work. +The ESLint flat config (introduced in LOU-B1) originally surfaced 271 pre-existing violations. They have since been fixed or downgraded; `npm run lint` (`eslint src`) now reports **0 errors and 428 warnings**. Warnings are tolerated for now: do not add new ones, and burn these down incrementally. -Total violations: 271 +Last refreshed: LOU-U5. Regenerate with `npx eslint src -f json` and group by `ruleId` / directory. -- src/agent-types/validators.ts:30 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/core/AgentBuilder.ts:94 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/core/AgentBuilder.ts:110 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/core/AgentBuilder.ts:118 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/core/AgentBuilder.ts:126 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/core/ConfigManager.test.ts:16 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/core/ConfigManager.ts:15 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/mocks.test.ts:9 [@typescript-eslint/no-unused-vars] 'Agent' is defined but never used. -- src/data/mocks.test.ts:9 [@typescript-eslint/no-unused-vars] 'Session' is defined but never used. -- src/data/mocks.test.ts:9 [@typescript-eslint/no-unused-vars] 'Result' is defined but never used. -- src/data/mocks.test.ts:9 [@typescript-eslint/no-unused-vars] 'Memory' is defined but never used. -- src/data/mocks.test.ts:9 [@typescript-eslint/no-unused-vars] 'Attachment' is defined but never used. -- src/data/mocks.ts:22 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/mocks.ts:40 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/mocks.ts:154 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/mocks.ts:166 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/mocks.ts:223 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:18 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:19 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:21 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:22 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:23 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:50 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:76 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:77 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:84 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:85 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:105 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:116 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:144 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:153 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:200 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/models.ts:214 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/repositories.ts:60 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/data/repositories.ts:61 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/AgentExecutor.ts:36 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/AgentExecutor.ts:263 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/AgentExecutor.ts:268 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/AgentExecutor.ts:298 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/AgentExecutor.ts:323 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/ContextBuilder.test.ts:20 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/ContextBuilder.ts:18 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/ContextBuilder.ts:29 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/ContextBuilder.ts:213 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/ContextBuilder.ts:235 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/MemoryManager.test.ts:8 [@typescript-eslint/no-unused-vars] 'Memory' is defined but never used. -- src/execution/MemoryManager.ts:24 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/execution/retry.ts:133 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowBuilder.ts:73 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowBuilder.ts:84 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.test.ts:6 [@typescript-eslint/no-unused-vars] 'FlowExecutionResult' is defined but never used. -- src/flows/FlowExecutor.test.ts:28 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.test.ts:155 [@typescript-eslint/no-unused-vars] 'events' is assigned a value but never used. -- src/flows/FlowExecutor.test.ts:480 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.test.ts:547 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:16 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:17 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:20 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:51 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:52 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:61 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:62 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:145 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:149 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:157 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:170 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:204 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:206 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:241 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:245 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:247 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:265 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:269 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:273 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:290 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:294 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:337 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:341 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:345 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:380 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:382 [@typescript-eslint/no-unused-vars] '_events' is defined but never used. -- src/flows/FlowExecutor.ts:383 [@typescript-eslint/no-unused-vars] '_onEvent' is defined but never used. -- src/flows/FlowExecutor.ts:384 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:393 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:464 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:468 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:493 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:524 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:528 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:549 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:551 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:559 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:561 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:568 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:577 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:577 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:577 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:585 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:597 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:597 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:597 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:608 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:622 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:622 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/FlowExecutor.ts:626 [@typescript-eslint/no-unused-vars] 'error' is defined but never used. -- src/flows/converters.ts:9 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/converters.ts:154 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/converters.ts:162 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/converters.ts:170 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/converters.ts:179 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/converters.ts:205 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:38 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:40 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:66 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:85 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:86 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:86 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:86 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:105 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:126 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/inputs.ts:176 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/flows/validators.test.ts:71 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:26 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:78 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:85 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:96 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:108 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:146 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:152 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:163 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:233 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OllamaProvider.ts:256 [@typescript-eslint/no-unused-vars] '_model' is defined but never used. -- src/providers/OllamaProvider.ts:267 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.test.ts:7 [@typescript-eslint/no-unused-vars] 'GenerateOptions' is defined but never used. -- src/providers/OpenAIProvider.ts:28 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:77 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:83 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:94 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:106 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:138 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:144 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:155 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:225 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenAIProvider.ts:248 [@typescript-eslint/no-unused-vars] '_model' is defined but never used. -- src/providers/OpenRouterProvider.examples.ts:238 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.test.ts:7 [@typescript-eslint/no-unused-vars] 'MessageRole' is defined but never used. -- src/providers/OpenRouterProvider.ts:28 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:112 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:118 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:129 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:141 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:173 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:179 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:190 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:260 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:304 [@typescript-eslint/no-unused-vars] '_model' is defined but never used. -- src/providers/OpenRouterProvider.ts:324 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:350 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/OpenRouterProvider.ts:363 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/llm.ts:43 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/llm.ts:76 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/llm.ts:98 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/llm.ts:171 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/providers/mock.ts:124 [@typescript-eslint/no-unused-vars] '_model' is defined but never used. -- src/providers/mock.ts:128 [@typescript-eslint/no-unused-vars] '_model' is defined but never used. -- src/providers/mock.ts:172 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/security/crypto.test.ts:93 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/security/crypto.ts:259 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/StorageService.test.ts:5 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/StorageService.test.ts:6 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/StorageService.ts:11 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/StorageService.ts:12 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/StorageService.ts:204 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/StorageService.ts:217 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/types.ts:27 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/storage/types.ts:47 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.test.ts:46 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.test.ts:194 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:17 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:31 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:31 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:41 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:55 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:82 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/TemplateManager.ts:109 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/types.ts:8 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/templates/types.ts:13 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:194 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:370 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:438 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:558 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:608 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:730 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:780 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:875 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:916 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1011 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1012 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1017 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1018 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1060 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1061 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1083 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1130 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1270 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1315 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/github.ts:1358 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:41 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:41 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:44 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:48 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:152 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:152 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/http.test.ts:155 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/index.test.ts:52 [@typescript-eslint/ban-ts-comment] Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free. -- src/tools/built-in/jira.ts:49 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:157 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:182 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:212 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:248 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:313 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:394 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:463 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:575 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:682 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:744 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:869 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:908 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:950 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:982 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1049 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1070 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1091 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1097 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1114 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1114 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/tools/built-in/jira.ts:1115 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:35 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:38 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:39 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:40 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:50 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:61 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/agent.ts:84 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:28 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:31 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:33 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:42 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:73 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:172 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/flow.ts:178 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/repository.ts:28 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/repository.ts:49 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/types/tool.ts:28 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/errors.ts:39 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/errors.ts:40 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/errors.ts:75 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/file-extractor.ts:7 [@typescript-eslint/no-unused-vars] 'Buffer' is defined but never used. -- src/utils/file-extractor.ts:7 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/file-extractor.ts:63 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/formatters.ts:35 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/formatters.ts:61 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.test.ts:44 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.test.ts:65 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.test.ts:66 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.test.ts:80 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.test.ts:81 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.ts:7 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.ts:66 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/json-path.ts:66 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. -- src/utils/validators.ts:71 [@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type. +## By rule + +| Rule | Warnings | +| --- | ---: | +| `@typescript-eslint/no-explicit-any` | 395 | +| `@typescript-eslint/no-unused-vars` | 22 | +| unused `eslint-disable` directives (no rule id) | 10 | +| `@typescript-eslint/ban-ts-comment` | 1 | + +## By directory (under `src/`) + +| Directory | Warnings | +| --- | ---: | +| `execution` | 123 | +| `flows` | 102 | +| `tools/built-in` | 54 | +| `data` | 28 | +| `providers` | 20 | +| `utils` | 19 | +| `types` | 18 | +| `templates` | 17 | +| `cli` | 10 | +| `tools/mcp` | 9 | +| `storage` | 8 | +| `core` | 6 | +| `deploy` (+ `deploy/adapters`) | 6 | +| `evals`, `security` | 3 each | +| `agent-types`, root | 1 each | + +Most of the work is replacing `any` in `execution/` and `flows/` (mainly `FlowExecutor` node handling and the executor option plumbing) with real types. diff --git a/docs/installation.md b/docs/installation.md index bee9357dc..3f990e6b1 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,7 +2,7 @@ ## Requirements -- Node.js **18 or newer** (`engines.node` in `package.json`). +- Node.js **22.19 or newer** (`engines.node` in `package.json`; the built-in `http` tool depends on `undici@8`, which needs it). - TypeScript is optional but recommended - the SDK ships full type definitions. diff --git a/package-lock.json b/package-lock.json index ef60560dd..12de0211e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "wrangler": "^4.143.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" }, "peerDependencies": { "@ai-sdk/anthropic": "^0.0.42", diff --git a/package.json b/package.json index ce35ffd52..4a388782a 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, "homepage": "https://github.com/LinuxDevil/agent-sdk#readme", "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" }, "peerDependencies": { "@ai-sdk/anthropic": "^0.0.42", diff --git a/scripts/verify-docs-snippets.ts b/scripts/verify-docs-snippets.ts index c2ac4f629..a088f7e8b 100644 --- a/scripts/verify-docs-snippets.ts +++ b/scripts/verify-docs-snippets.ts @@ -19,6 +19,28 @@ * --skip-build reuse the current dist/ instead of running `npm run build` * --keep keep the temp project (its path is printed) for debugging * + * Two stages (LOU-U5): + * + * A. SOURCE CHECK - every ```ts / ```typescript block in README.md and + * docs/*.md (all of them, not just the quick start) is type-checked + * against the real SDK SOURCE: `@loushy/build-ai-agent` and its subpaths + * are resolved to `src/` through `paths` derived from package.json + * `exports`, so a docs example can never drift from the code (wrong + * argument order, methods that do not exist, ...). Types only; nothing + * is executed. + * B. PACKED RUN - the quick-start docs (the "paste it and it works" path) + * are additionally type-checked AND run against the packed tarball, as + * described above. + * + * Intentionally partial snippets (they use `agent`, `provider`, `storage`... + * without defining them) do NOT need an opt-out: stage A prepends an + * ambient preamble (PLACEHOLDERS below) that declares those + * common names as typed globals. A snippet's own `const agent = ...` + * shadows the placeholder, so only genuinely free names are covered. To add + * a placeholder, add an entry to PLACEHOLDERS. For a block that + * cannot be checked at all, put `no-verify` in its fence info + * (```ts no-verify) - it is skipped by both stages. + * * Snippets run with provider credential env vars (OPENAI_API_KEY, ...) * removed, so they exercise the mock-provider path deterministically and * never make network calls. A fence whose info string contains @@ -30,7 +52,28 @@ import * as os from 'node:os'; import * as path from 'node:path'; const REPO_ROOT = path.resolve(__dirname, '..'); -const DEFAULT_DOCS = [path.join(REPO_ROOT, 'docs', 'quick-start.md')]; +/** Docs that are type-checked AND executed against the packed tarball (stage B). */ +const RUNNABLE_DOCS = [path.join(REPO_ROOT, 'docs', 'quick-start.md')]; +/** Ambient names (typed against the SDK where possible) that intentionally-partial snippets may use without defining. */ +const SDK = "import('@loushy/build-ai-agent')"; +const PLACEHOLDERS: Record = { + agent: `${SDK}.AgentConfig`, + provider: `${SDK}.LLMProvider`, + registry: `${SDK}.ToolRegistry`, + toolRegistry: `${SDK}.ToolRegistry`, + storage: `${SDK}.StorageService`, + approvalStore: `${SDK}.ApprovalStore`, + checkpointStore: `${SDK}.CheckpointStore`, + mcpClient: "import('@modelcontextprotocol/sdk/client/index.js').Client", + input: 'string', + patch: 'string', + repoPath: 'string', + file: 'File', + emailTool: 'any', +}; +const PLACEHOLDER_DECLARATIONS = Object.entries(PLACEHOLDERS) + .map(([name, type]) => `declare var ${name}: ${type};`) + .join('\n'); const CREDENTIAL_ENV_VARS = ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OPENROUTER_API_KEY', 'OLLAMA_BASE_URL']; const SNIPPET_TIMEOUT_MS = 60_000; @@ -229,48 +272,139 @@ function runSnippets(projectDir: string, files: string[], snippets: Snippet[], t } function loadSnippets(docs: string[]): Snippet[] { - return (docs.length > 0 ? docs : DEFAULT_DOCS).flatMap((file) => - extractSnippets(fs.readFileSync(file, 'utf8'), path.relative(REPO_ROOT, file)) - ); + return docs.flatMap((file) => extractSnippets(fs.readFileSync(file, 'utf8'), path.relative(REPO_ROOT, file))); } -function reportFailures(failures: Failure[]): void { - console.error(`\nverify-docs-snippets: ${failures.length} failure(s)`); - for (const f of failures) { - console.error(`\n[${f.stage}] ${f.snippet.file}:${f.snippet.line}\n${f.output}`); +/** README.md plus every docs/*.md page (the default stage A input). */ +function allDocFiles(): string[] { + const docsDir = path.join(REPO_ROOT, 'docs'); + const pages = fs.readdirSync(docsDir).filter((f) => f.endsWith('.md')).sort(); + return [path.join(REPO_ROOT, 'README.md'), ...pages.map((f) => path.join(docsDir, f))]; +} + +/** tsconfig `paths` mapping each package.json export's types entry (dist/*.d.ts) to its src/*.ts source. */ +function sourcePaths(): Record { + const pkg = readJson(path.join(REPO_ROOT, 'package.json')); + const paths: Record = {}; + for (const [subpath, target] of Object.entries<{ types: string }>(pkg.exports)) { + const specifier = subpath === '.' ? pkg.name : `${pkg.name}/${subpath.slice(2)}`; + paths[specifier] = [target.types.replace(/^\.\/dist\//, 'src/').replace(/\.d\.ts$/, '.ts')]; } + return paths; } -function main(): void { - const args = process.argv.slice(2); - const skipBuild = args.includes('--skip-build'); - const keep = args.includes('--keep'); - const docs = args.filter((a) => !a.startsWith('--')).map((a) => path.resolve(a)); +/** Writes the stage A tsconfig + placeholder preamble and one `snippet-.ts` per snippet; returns the file names. */ +function writeSourceProject(dir: string, snippets: Snippet[]): string[] { + fs.writeFileSync(path.join(dir, 'placeholders.d.ts'), `${PLACEHOLDER_DECLARATIONS}\n`); + fs.writeFileSync( + path.join(dir, 'tsconfig.json'), + JSON.stringify({ + extends: '../tsconfig.json', + compilerOptions: { + rootDir: '..', + baseUrl: '..', + paths: sourcePaths(), + noEmit: true, + declaration: false, + declarationMap: false, + sourceMap: false, + noUnusedLocals: false, + noUnusedParameters: false, + }, + include: ['./*.ts', '../typings/**/*.d.ts'], + exclude: [], + }) + ); + return snippets.map((snippet, index) => { + const name = `snippet-${index + 1}.ts`; + fs.writeFileSync(path.join(dir, name), `// ${snippet.file}:${snippet.line}\n${snippet.source}\nexport {};\n`); + return name; + }); +} - const snippets = loadSnippets(docs); - if (snippets.length === 0) { - console.error('verify-docs-snippets: no ```ts snippets found'); - process.exit(1); +/** Maps tsc output lines (`/snippet-N.ts(...)`) back to the snippet that produced them. */ +function collectTypeFailures(output: string, dir: string, files: string[], snippets: Snippet[], ok: boolean): Failure[] { + const lines = output.split(/\r?\n/).map((l) => l.replace(/\\/g, '/')); + const prefix = path.relative(REPO_ROOT, dir).replace(/\\/g, '/'); + const failures: Failure[] = []; + files.forEach((name, index) => { + const errors = lines.filter((l) => l.startsWith(`${prefix}/${name}(`)); + if (errors.length > 0) failures.push({ snippet: snippets[index], stage: 'typecheck', output: errors.join('\n') }); + }); + if (!ok && failures.length === 0) throw new Error(`tsc failed without per-snippet errors:\n${output}`); + return failures; +} + +/** + * Stage A: type-checks every snippet against src/ (not the built package). + * The temp project lives inside the repo so `ai`, `zod`, ... resolve from the + * repo's node_modules; it is always removed afterwards. + */ +function checkAgainstSource(snippets: Snippet[]): Failure[] { + console.log(`- type-checking ${snippets.length} snippet(s) against src/ (tsc --noEmit)...`); + const dir = fs.mkdtempSync(path.join(REPO_ROOT, '.docs-snippets-')); + try { + const files = writeSourceProject(dir, snippets); + const tsc = path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'); + const result = spawnSync(process.execPath, [tsc, '-p', dir], { cwd: REPO_ROOT, encoding: 'utf8' }); + return collectTypeFailures(`${result.stdout}${result.stderr}`, dir, files, snippets, result.status === 0); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); } - console.log(`verify-docs-snippets: ${snippets.length} snippet(s) found`); +} +/** Stage B: type-check and run the snippets against the packed tarball in a throwaway project. */ +function verifyPacked(snippets: Snippet[], skipBuild: boolean, keep: boolean): Failure[] { const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'loushy-docs-snippets-')); - const failures: Failure[] = []; try { setUpProject(projectDir, skipBuild); const files = writeSnippetFiles(projectDir, snippets); const typeFailures = typeCheckSnippets(projectDir, files, snippets); - failures.push(...typeFailures, ...runSnippets(projectDir, files, snippets, typeFailures)); + return [...typeFailures, ...runSnippets(projectDir, files, snippets, typeFailures)]; } finally { if (keep) console.log(`- temp project kept at ${projectDir}`); else fs.rmSync(projectDir, { recursive: true, force: true }); } +} +function reportFailures(failures: Failure[]): void { + console.error(`\nverify-docs-snippets: ${failures.length} failure(s)`); + for (const f of failures) { + console.error(`\n[${f.stage}] ${f.snippet.file}:${f.snippet.line}\n${f.output}`); + } +} + +function orDefault(explicit: string[], fallback: () => string[]): string[] { + return explicit.length > 0 ? explicit : fallback(); +} + +/** Loads the stage A (all docs) and stage B (runnable docs) snippet sets, honouring explicit file arguments. */ +function loadSnippetSets(docs: string[]): { sourceSnippets: Snippet[]; runnableSnippets: Snippet[] } { + const sourceSnippets = loadSnippets(orDefault(docs, allDocFiles)); + const runnableSnippets = loadSnippets(orDefault(docs, () => RUNNABLE_DOCS)); + if (Math.min(sourceSnippets.length, runnableSnippets.length) === 0) { + console.error('verify-docs-snippets: no ```ts snippets found'); + process.exit(1); + } + return { sourceSnippets, runnableSnippets }; +} + +function main(): void { + const args = process.argv.slice(2); + const docs = args.filter((a) => !a.startsWith('--')).map((a) => path.resolve(a)); + const { sourceSnippets, runnableSnippets } = loadSnippetSets(docs); + console.log(`verify-docs-snippets: ${sourceSnippets.length} snippet(s) found (${runnableSnippets.length} runnable)`); + + const packed = verifyPacked(runnableSnippets, args.includes('--skip-build'), args.includes('--keep')); + const failures = [...checkAgainstSource(sourceSnippets), ...packed]; if (failures.length > 0) { reportFailures(failures); process.exit(1); } - console.log(`\nverify-docs-snippets: all ${snippets.length} snippet(s) type-check and run cleanly`); + console.log( + ` +verify-docs-snippets: all ${sourceSnippets.length} snippet(s) type-check against src/; ${runnableSnippets.length} also run cleanly` + ); } if (require.main === module) {