diff --git a/.gitignore b/.gitignore index c5226b51..0bad302e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # dependencies (bun install) node_modules +btca.config.jsonc .claude/ diff --git a/apps/cli/package.json b/apps/cli/package.json index 887a3680..cbf73382 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "btca", "author": "Ben Davis", - "version": "2.0.3", + "version": "2.0.4", "homepage": "https://btca.dev", "description": "CLI tool for asking questions about technologies using btca server", "type": "module", diff --git a/apps/server/package.json b/apps/server/package.json index f3c567e8..e5e5ddd1 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "btca-server", - "version": "2.0.3", + "version": "2.0.4", "description": "BTCA server for answering questions about your codebase using OpenCode AI", "author": "Ben Davis", "license": "MIT", diff --git a/apps/server/src/agent/loop.ts b/apps/server/src/agent/loop.ts index 4e086a15..d0d8d1b6 100644 --- a/apps/server/src/agent/loop.ts +++ b/apps/server/src/agent/loop.ts @@ -57,35 +57,57 @@ export type AgentLoopResult = { events: AgentEvent[]; }; +const BASE_PROMPT = ` +You are btca, an expert research agent. Your job is to answer questions from the user by searching the resources at your disposal. + + +- Persona: an expert professional researcher +- Channel: internal +- Emotional register: direct, calm, and concise +- Formatting: bulleted/numbered lists are good + codeblocks +- Length: be thorough with your response, don't let it get too long though +- Default follow-through: don't ask permission to do the research, just do it and answer the question. ask for clarifications + suggest good follow up if needed + + + +- When multiple retrieval or lookup steps are independent, prefer parallel tool calls to reduce wall-clock time. +- Do not parallelize steps that have prerequisite dependencies or where one result determines the next action. +- After parallel retrieval, pause to synthesize the results before making more calls. +- Prefer selective parallelism: parallelize independent evidence gathering, not speculative or redundant tool use. + + + +- Use tools whenever they materially improve correctness, completeness, or grounding. +- Do NOT stop early to save tool calls. +- Keep calling tools until either: + 1) the task is complete + 2) you've hit a doom loop where none of the tools function or something is missing +- If a tool returns empty/partial results, retry with a different strategy (query, filters, alternate source). + + + +- Treat the task as incomplete until you have a complete answer to the user's question that's grounded +- If any item is blocked by missing data, mark it [blocked] and state exactly what is missing. + + + +- Don't stop at the first plausible answer. +- Look for second-order issues, edge cases, and missing constraints. + + + +- Return a thorough answer to the user's question with real code examples +- Always output in proper markdown format +- Always include sources for your answer: + - For git resources, source links must be full github blob urls + - In "Sources", format git citations as markdown links: - [repo/relative/path.ext](https://github.com/.../blob/.../repo/relative/path.ext)".' + - For local resources cite local file paths + - For npm resources cite the path in the npm package + +`; + const buildSystemPrompt = (agentInstructions: string): string => - [ - 'You are btca, an expert documentation search agent.', - 'Your job is to answer questions by searching through the collection of resources.', - '', - 'You have access to the following tools:', - '- read: Read file contents with line numbers', - '- grep: Search file contents using regex patterns', - '- glob: Find files matching glob patterns', - '- list: List directory contents', - '', - 'Guidelines:', - '- Ground answers in the loaded resources. Do not rely on unstated prior knowledge.', - '- Search efficiently: start with one focused list/glob pass, then read likely files; only expand search when evidence is insufficient.', - '- Prefer targeted grep/read over broad repeated scans once candidate files are known.', - '- Give clear, unambiguous answers. State assumptions, prerequisites, and important version-sensitive caveats.', - '- For implementation/how-to questions, provide complete step-by-step instructions with commands and code snippets.', - '- Be concise but thorough in your responses.', - '- End every answer with a "Sources" section.', - '- For git resources, source links must be full GitHub blob URLs.', - '- In "Sources", format git citations as markdown links: "- [repo/relative/path.ext](https://github.com/.../blob/.../repo/relative/path.ext)".', - '- Do not use raw URLs as link labels.', - '- Do not repeat a URL in parentheses after a link.', - '- Do not output sources in "url (url)" format.', - '- For local resources, cite local file paths (no GitHub URL required).', - '- If you cannot find the answer, say so clearly', - '', - agentInstructions - ].join('\n'); + [BASE_PROMPT, agentInstructions].join('\n'); const createTools = (basePath: string, vfsId?: string) => ({ read: tool({ diff --git a/apps/server/src/collections/service.test.ts b/apps/server/src/collections/service.test.ts index 23fa603d..cd5d3b83 100644 --- a/apps/server/src/collections/service.test.ts +++ b/apps/server/src/collections/service.test.ts @@ -4,31 +4,45 @@ import os from 'node:os'; import path from 'node:path'; import type { ConfigService } from '../config/index.ts'; +import type { ResourceDefinition } from '../resources/schema.ts'; import type { ResourcesService } from '../resources/service.ts'; +import type { BtcaFsResource } from '../resources/types.ts'; import { createCollectionsService } from './service.ts'; import { disposeVirtualFs, existsInVirtualFs } from '../vfs/virtual-fs.ts'; -const createLocalResource = (name: string, resourcePath: string) => ({ +const createFsResource = ({ + name, + resourcePath, + type = 'local', + repoSubPaths = [], + specialAgentInstructions = '' +}: { + name: string; + resourcePath: string; + type?: BtcaFsResource['type']; + repoSubPaths?: readonly string[]; + specialAgentInstructions?: string; +}) => ({ _tag: 'fs-based' as const, name, fsName: name, - type: 'local' as const, - repoSubPaths: [], - specialAgentInstructions: '', + type, + repoSubPaths, + specialAgentInstructions, getAbsoluteDirectoryPath: async () => resourcePath }); -const createConfigMock = () => +const createConfigMock = (definitions: Record = {}) => ({ - getResource: () => undefined + getResource: (name: string) => definitions[name] }) as unknown as ConfigService; -const createResourcesMock = (resourcePath: string) => +const createResourcesMock = (loadPromise: ResourcesService['loadPromise']) => ({ load: () => { throw new Error('Not implemented in test'); }, - loadPromise: async () => createLocalResource('repo', resourcePath) + loadPromise }) as unknown as ResourcesService; const runGit = (cwd: string, args: string[]) => { @@ -55,7 +69,7 @@ describe('createCollectionsService', () => { const resourcePath = await fs.mkdtemp(path.join(os.tmpdir(), 'btca-collections-git-')); const collections = createCollectionsService({ config: createConfigMock(), - resources: createResourcesMock(resourcePath) + resources: createResourcesMock(async () => createFsResource({ name: 'repo', resourcePath })) }); try { @@ -89,7 +103,7 @@ describe('createCollectionsService', () => { const resourcePath = await fs.mkdtemp(path.join(os.tmpdir(), 'btca-collections-local-')); const collections = createCollectionsService({ config: createConfigMock(), - resources: createResourcesMock(resourcePath) + resources: createResourcesMock(async () => createFsResource({ name: 'repo', resourcePath })) }); try { @@ -109,6 +123,129 @@ describe('createCollectionsService', () => { false ); expect(await existsInVirtualFs('/repo/dist/bundle.js', collection.vfsId)).toBe(false); + expect(collection.agentInstructions).not.toContain(''); + } finally { + await cleanupCollection(collection); + } + } finally { + await fs.rm(resourcePath, { recursive: true, force: true }); + } + }); + + it('includes git citation metadata in agent instructions', async () => { + const resourcePath = await fs.mkdtemp(path.join(os.tmpdir(), 'btca-collections-git-meta-')); + const collections = createCollectionsService({ + config: createConfigMock({ + docs: { + type: 'git', + name: 'docs', + url: 'https://github.com/example/repo.git', + branch: 'main', + searchPath: 'guides', + specialNotes: 'Prefer the guides folder.' + } + }), + resources: createResourcesMock(async () => + createFsResource({ + name: 'docs', + resourcePath, + type: 'git', + repoSubPaths: ['guides'], + specialAgentInstructions: 'Prefer the guides folder.' + }) + ) + }); + + try { + await fs.writeFile(path.join(resourcePath, 'README.md'), 'hello\n'); + runGit(resourcePath, ['init', '-q']); + runGit(resourcePath, ['config', 'user.email', 'test@example.com']); + runGit(resourcePath, ['config', 'user.name', 'BTCA Test']); + runGit(resourcePath, ['add', 'README.md']); + runGit(resourcePath, ['commit', '-m', 'init']); + + const collection = await collections.loadPromise({ resourceNames: ['docs'] }); + + try { + expect(collection.agentInstructions).toContain( + 'https://github.com/example/repo' + ); + expect(collection.agentInstructions).toContain('main'); + expect(collection.agentInstructions).toContain( + 'https://github.com/example/repo/blob/main' + ); + expect(collection.agentInstructions).toContain( + 'Convert virtual paths under ./docs/ to repo-relative paths, then encode each path segment for GitHub URLs.' + ); + expect(collection.agentInstructions).toContain('./docs/guides'); + expect(collection.agentInstructions).toContain(''); + expect(collection.agentInstructions).toContain( + 'Prefer the guides folder.' + ); + } finally { + await cleanupCollection(collection); + } + } finally { + await fs.rm(resourcePath, { recursive: true, force: true }); + } + }); + + it('includes npm citation metadata in agent instructions', async () => { + const resourcePath = await fs.mkdtemp(path.join(os.tmpdir(), 'btca-collections-npm-meta-')); + const collections = createCollectionsService({ + config: createConfigMock({ + react: { + type: 'npm', + name: 'react', + package: 'react', + version: '19.0.0', + specialNotes: 'Use package docs.' + } + }), + resources: createResourcesMock(async () => + createFsResource({ + name: 'react', + resourcePath, + type: 'npm', + specialAgentInstructions: 'Use package docs.' + }) + ) + }); + + try { + await fs.writeFile( + path.join(resourcePath, '.btca-npm-meta.json'), + JSON.stringify({ + packageName: 'react', + resolvedVersion: '19.0.0', + packageUrl: 'https://www.npmjs.com/package/react' + }) + ); + await fs.writeFile(path.join(resourcePath, 'README.md'), 'react docs\n'); + + const collection = await collections.loadPromise({ resourceNames: ['react'] }); + + try { + expect(collection.agentInstructions).toContain('react'); + expect(collection.agentInstructions).toContain('19.0.0'); + expect(collection.agentInstructions).toContain( + 'https://www.npmjs.com/package/react' + ); + expect(collection.agentInstructions).toContain( + 'npm:react@19.0.0' + ); + expect(collection.agentInstructions).toContain( + 'https://unpkg.com/react@19.0.0' + ); + expect(collection.agentInstructions).toContain( + 'In Sources, cite npm files using npm:react@19.0.0/<file> and link them to https://unpkg.com/react@19.0.0/<file>. Do not cite encoded virtual folder names.' + ); + expect(collection.agentInstructions).toContain( + 'https://unpkg.com/react@19.0.0/package.json' + ); + expect(collection.agentInstructions).toContain( + 'Use package docs.' + ); } finally { await cleanupCollection(collection); } diff --git a/apps/server/src/collections/service.ts b/apps/server/src/collections/service.ts index af225650..a8e93934 100644 --- a/apps/server/src/collections/service.ts +++ b/apps/server/src/collections/service.ts @@ -36,56 +36,110 @@ export type CollectionsService = { }) => Promise; }; -const encodePathSegments = (value: string) => value.split('/').map(encodeURIComponent).join('/'); +const escapeXml = (value: string) => + value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + +const getResourceTypeLabel = (resource: BtcaFsResource) => { + if (resource.type === 'git') return 'git repo'; + if (resource.type === 'npm') return 'npm package'; + return 'local directory'; +}; const trimGitSuffix = (url: string) => url.replace(/\.git$/u, '').replace(/\/+$/u, ''); + +const encodePathSegments = (value: string) => value.split('/').map(encodeURIComponent).join('/'); + +const xmlLine = (tag: string, value?: string) => + value ? `\t\t<${tag}>${escapeXml(value)}` : ''; + +const xmlPathBlock = (tag: string, values: readonly string[], prefix = '') => + values.length === 0 + ? '' + : [ + `\t\t<${tag}>`, + ...values.map((value) => `\t\t\t${escapeXml(`${prefix}${value}`)}`), + `\t\t` + ].join('\n'); + const getNpmCitationAlias = (metadata?: VirtualResourceMetadata) => { if (!metadata?.package) return undefined; return `npm:${metadata.package}@${metadata.version ?? 'latest'}`; }; +const getNpmFileUrlPrefix = (metadata?: VirtualResourceMetadata) => { + if (!metadata?.package || !metadata?.version) return undefined; + return `https://unpkg.com/${metadata.package}@${metadata.version}`; +}; + const createCollectionInstructionBlock = ( resource: BtcaFsResource, metadata?: VirtualResourceMetadata -): string => { - const focusLines = resource.repoSubPaths.map( - (subPath) => `Focus: ./${resource.fsName}/${subPath}` - ); +) => { + const repoUrl = + resource.type === 'git' && metadata?.url ? trimGitSuffix(metadata.url) : undefined; const gitRef = metadata?.branch ?? metadata?.commit; - const githubPrefix = - resource.type === 'git' && metadata?.url && gitRef - ? `${trimGitSuffix(metadata.url)}/blob/${encodeURIComponent(gitRef)}` - : undefined; + const githubBlobPrefix = + repoUrl && gitRef ? `${repoUrl}/blob/${encodeURIComponent(gitRef)}` : undefined; const npmCitationAlias = resource.type === 'npm' ? getNpmCitationAlias(metadata) : undefined; - const lines = [ - `## Resource: ${resource.name}`, - FS_RESOURCE_SYSTEM_NOTE, - `Path: ./${resource.fsName}`, - resource.type === 'git' && metadata?.url ? `Repo URL: ${trimGitSuffix(metadata.url)}` : '', - resource.type === 'git' && metadata?.branch ? `Repo Branch: ${metadata.branch}` : '', - resource.type === 'git' && metadata?.commit ? `Repo Commit: ${metadata.commit}` : '', - resource.type === 'npm' && metadata?.package ? `NPM Package: ${metadata.package}` : '', - resource.type === 'npm' && metadata?.version ? `NPM Version: ${metadata.version}` : '', - resource.type === 'npm' && metadata?.url ? `NPM URL: ${metadata.url}` : '', - npmCitationAlias ? `NPM Citation Alias: ${npmCitationAlias}` : '', - githubPrefix ? `GitHub Blob Prefix: ${githubPrefix}` : '', - githubPrefix - ? `GitHub Citation Rule: Convert virtual paths under ./${resource.fsName}/ to repo-relative paths, then encode each path segment for GitHub URLs (example segment: "+page.server.js" -> "${encodeURIComponent('+page.server.js')}").` - : '', - githubPrefix - ? `GitHub Citation Example: ${githubPrefix}/${encodePathSegments('src/routes/blog/+page.server.js')}` - : '', - resource.type !== 'git' - ? 'Citation Rule: Cite local file paths only for this resource (no GitHub URL).' - : '', - npmCitationAlias - ? `NPM Citation Rule: In "Sources", cite npm files using "${npmCitationAlias}/" (for example, "${npmCitationAlias}/README.md"). Do not cite encoded virtual folder names.` - : '', - ...focusLines, - resource.specialAgentInstructions ? `Notes: ${resource.specialAgentInstructions}` : '' - ].filter(Boolean); - - return lines.join('\n'); + const npmFileUrlPrefix = resource.type === 'npm' ? getNpmFileUrlPrefix(metadata) : undefined; + + return [ + '\t', + `\t\t${escapeXml(resource.name)}`, + `\t\t${getResourceTypeLabel(resource)}`, + `\t\t${escapeXml(FS_RESOURCE_SYSTEM_NOTE)}`, + `\t\t${escapeXml(`./${resource.fsName}`)}`, + xmlLine('repo_url', repoUrl), + xmlLine('repo_branch', resource.type === 'git' ? metadata?.branch : undefined), + xmlLine('repo_commit', resource.type === 'git' ? metadata?.commit : undefined), + xmlLine('npm_package', resource.type === 'npm' ? metadata?.package : undefined), + xmlLine('npm_version', resource.type === 'npm' ? metadata?.version : undefined), + xmlLine('npm_url', resource.type === 'npm' ? metadata?.url : undefined), + xmlLine('npm_citation_alias', npmCitationAlias), + xmlLine('npm_file_url_prefix', npmFileUrlPrefix), + xmlLine('github_blob_prefix', githubBlobPrefix), + xmlLine( + 'citation_rule', + githubBlobPrefix + ? `Convert virtual paths under ./${resource.fsName}/ to repo-relative paths, then encode each path segment for GitHub URLs.` + : resource.type === 'npm' && npmCitationAlias + ? `In Sources, cite npm files using ${npmCitationAlias}/ and link them to ${npmFileUrlPrefix ?? 'the exact file URL prefix'}/. Do not cite encoded virtual folder names.` + : 'Cite local file paths only for this resource.' + ), + xmlLine( + 'citation_example', + githubBlobPrefix + ? `${githubBlobPrefix}/${encodePathSegments('src/routes/blog/+page.server.js')}` + : resource.type === 'npm' && npmCitationAlias && npmFileUrlPrefix + ? `${npmFileUrlPrefix}/package.json` + : undefined + ), + xmlPathBlock('focus_paths', resource.repoSubPaths, `./${resource.fsName}/`), + xmlLine('special_notes', resource.specialAgentInstructions), + '\t' + ] + .filter(Boolean) + .join('\n'); +}; + +const createCollectionInstructions = ( + resources: readonly BtcaFsResource[], + metadataResources: readonly VirtualResourceMetadata[] +) => { + const metadataByName = new Map(metadataResources.map((resource) => [resource.name, resource])); + + return [ + '', + ...resources.map((resource) => + createCollectionInstructionBlock(resource, metadataByName.get(resource.name)) + ), + '' + ].join('\n'); }; const ignoreErrors = async (action: () => Promise) => { @@ -400,16 +454,9 @@ export const createCollectionsService = (args: { resources: metadataResources }); - const metadataByName = new Map( - metadataResources.map((resource) => [resource.name, resource]) - ); - const instructionBlocks = loadedResources.map((resource) => - createCollectionInstructionBlock(resource, metadataByName.get(resource.name)) - ); - return { path: collectionPath, - agentInstructions: instructionBlocks.join('\n\n'), + agentInstructions: createCollectionInstructions(loadedResources, metadataResources), vfsId, cleanup: async () => { await cleanupResources(loadedResources); diff --git a/apps/web/src/convex/_generated/api.d.ts b/apps/web/src/convex/_generated/api.d.ts index 7c558585..6d941959 100644 --- a/apps/web/src/convex/_generated/api.d.ts +++ b/apps/web/src/convex/_generated/api.d.ts @@ -8,76 +8,72 @@ * @module */ -import type * as analytics from "../analytics.js"; -import type * as analyticsEvents from "../analyticsEvents.js"; -import type * as apiHelpers from "../apiHelpers.js"; -import type * as authHelpers from "../authHelpers.js"; -import type * as clerkApiKeys from "../clerkApiKeys.js"; -import type * as clerkApiKeysQueries from "../clerkApiKeysQueries.js"; -import type * as crons from "../crons.js"; -import type * as githubAuth from "../githubAuth.js"; -import type * as githubConnections from "../githubConnections.js"; -import type * as http from "../http.js"; -import type * as instances_actions from "../instances/actions.js"; -import type * as instances_mutations from "../instances/mutations.js"; -import type * as instances_queries from "../instances/queries.js"; -import type * as mcp from "../mcp.js"; -import type * as mcp_resourceContract from "../mcp/resourceContract.js"; -import type * as mcpInternal from "../mcpInternal.js"; -import type * as mcpQuestions from "../mcpQuestions.js"; -import type * as messages from "../messages.js"; -import type * as migrations from "../migrations.js"; -import type * as privateWrappers from "../privateWrappers.js"; -import type * as projects from "../projects.js"; -import type * as resourceActions from "../resourceActions.js"; -import type * as resources from "../resources.js"; -import type * as scheduled_queries from "../scheduled/queries.js"; -import type * as scheduled_updates from "../scheduled/updates.js"; -import type * as scheduled_versionCheck from "../scheduled/versionCheck.js"; -import type * as streamSessions from "../streamSessions.js"; -import type * as threadTitle from "../threadTitle.js"; -import type * as threads from "../threads.js"; -import type * as usage from "../usage.js"; -import type * as users from "../users.js"; +import type * as analytics from '../analytics.js'; +import type * as analyticsEvents from '../analyticsEvents.js'; +import type * as apiHelpers from '../apiHelpers.js'; +import type * as authHelpers from '../authHelpers.js'; +import type * as clerkApiKeys from '../clerkApiKeys.js'; +import type * as clerkApiKeysQueries from '../clerkApiKeysQueries.js'; +import type * as crons from '../crons.js'; +import type * as githubAuth from '../githubAuth.js'; +import type * as githubConnections from '../githubConnections.js'; +import type * as http from '../http.js'; +import type * as instances_actions from '../instances/actions.js'; +import type * as instances_mutations from '../instances/mutations.js'; +import type * as instances_queries from '../instances/queries.js'; +import type * as mcp from '../mcp.js'; +import type * as mcp_resourceContract from '../mcp/resourceContract.js'; +import type * as mcpInternal from '../mcpInternal.js'; +import type * as mcpQuestions from '../mcpQuestions.js'; +import type * as messages from '../messages.js'; +import type * as migrations from '../migrations.js'; +import type * as privateWrappers from '../privateWrappers.js'; +import type * as projects from '../projects.js'; +import type * as resourceActions from '../resourceActions.js'; +import type * as resources from '../resources.js'; +import type * as scheduled_queries from '../scheduled/queries.js'; +import type * as scheduled_updates from '../scheduled/updates.js'; +import type * as scheduled_versionCheck from '../scheduled/versionCheck.js'; +import type * as streamSessions from '../streamSessions.js'; +import type * as threadTitle from '../threadTitle.js'; +import type * as threads from '../threads.js'; +import type * as usage from '../usage.js'; +import type * as users from '../users.js'; -import type { - ApiFromModules, - FilterApi, - FunctionReference, -} from "convex/server"; +import type { ApiFromModules, FilterApi, FunctionReference } from 'convex/server'; declare const fullApi: ApiFromModules<{ - analytics: typeof analytics; - analyticsEvents: typeof analyticsEvents; - apiHelpers: typeof apiHelpers; - authHelpers: typeof authHelpers; - clerkApiKeys: typeof clerkApiKeys; - clerkApiKeysQueries: typeof clerkApiKeysQueries; - crons: typeof crons; - githubAuth: typeof githubAuth; - githubConnections: typeof githubConnections; - http: typeof http; - "instances/actions": typeof instances_actions; - "instances/mutations": typeof instances_mutations; - "instances/queries": typeof instances_queries; - mcp: typeof mcp; - "mcp/resourceContract": typeof mcp_resourceContract; - mcpInternal: typeof mcpInternal; - mcpQuestions: typeof mcpQuestions; - messages: typeof messages; - migrations: typeof migrations; - privateWrappers: typeof privateWrappers; - projects: typeof projects; - resourceActions: typeof resourceActions; - resources: typeof resources; - "scheduled/queries": typeof scheduled_queries; - "scheduled/updates": typeof scheduled_updates; - "scheduled/versionCheck": typeof scheduled_versionCheck; - streamSessions: typeof streamSessions; - threadTitle: typeof threadTitle; - threads: typeof threads; - usage: typeof usage; - users: typeof users; + analytics: typeof analytics; + analyticsEvents: typeof analyticsEvents; + apiHelpers: typeof apiHelpers; + authHelpers: typeof authHelpers; + clerkApiKeys: typeof clerkApiKeys; + clerkApiKeysQueries: typeof clerkApiKeysQueries; + crons: typeof crons; + githubAuth: typeof githubAuth; + githubConnections: typeof githubConnections; + http: typeof http; + 'instances/actions': typeof instances_actions; + 'instances/mutations': typeof instances_mutations; + 'instances/queries': typeof instances_queries; + mcp: typeof mcp; + 'mcp/resourceContract': typeof mcp_resourceContract; + mcpInternal: typeof mcpInternal; + mcpQuestions: typeof mcpQuestions; + messages: typeof messages; + migrations: typeof migrations; + privateWrappers: typeof privateWrappers; + projects: typeof projects; + resourceActions: typeof resourceActions; + resources: typeof resources; + 'scheduled/queries': typeof scheduled_queries; + 'scheduled/updates': typeof scheduled_updates; + 'scheduled/versionCheck': typeof scheduled_versionCheck; + streamSessions: typeof streamSessions; + threadTitle: typeof threadTitle; + threads: typeof threads; + usage: typeof usage; + users: typeof users; }>; /** @@ -88,10 +84,7 @@ declare const fullApi: ApiFromModules<{ * const myFunctionReference = api.myModule.myFunction; * ``` */ -export declare const api: FilterApi< - typeof fullApi, - FunctionReference ->; +export declare const api: FilterApi>; /** * A utility for referencing Convex functions in your app's internal API. @@ -101,96 +94,88 @@ export declare const api: FilterApi< * const myFunctionReference = internal.myModule.myFunction; * ``` */ -export declare const internal: FilterApi< - typeof fullApi, - FunctionReference ->; +export declare const internal: FilterApi>; export declare const components: { - migrations: { - lib: { - cancel: FunctionReference< - "mutation", - "internal", - { name: string }, - { - batchSize?: number; - cursor?: string | null; - error?: string; - isDone: boolean; - latestEnd?: number; - latestStart: number; - name: string; - next?: Array; - processed: number; - state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; - } - >; - cancelAll: FunctionReference< - "mutation", - "internal", - { sinceTs?: number }, - Array<{ - batchSize?: number; - cursor?: string | null; - error?: string; - isDone: boolean; - latestEnd?: number; - latestStart: number; - name: string; - next?: Array; - processed: number; - state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; - }> - >; - clearAll: FunctionReference< - "mutation", - "internal", - { before?: number }, - null - >; - getStatus: FunctionReference< - "query", - "internal", - { limit?: number; names?: Array }, - Array<{ - batchSize?: number; - cursor?: string | null; - error?: string; - isDone: boolean; - latestEnd?: number; - latestStart: number; - name: string; - next?: Array; - processed: number; - state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; - }> - >; - migrate: FunctionReference< - "mutation", - "internal", - { - batchSize?: number; - cursor?: string | null; - dryRun: boolean; - fnHandle: string; - name: string; - next?: Array<{ fnHandle: string; name: string }>; - oneBatchOnly?: boolean; - }, - { - batchSize?: number; - cursor?: string | null; - error?: string; - isDone: boolean; - latestEnd?: number; - latestStart: number; - name: string; - next?: Array; - processed: number; - state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; - } - >; - }; - }; + migrations: { + lib: { + cancel: FunctionReference< + 'mutation', + 'internal', + { name: string }, + { + batchSize?: number; + cursor?: string | null; + error?: string; + isDone: boolean; + latestEnd?: number; + latestStart: number; + name: string; + next?: Array; + processed: number; + state: 'inProgress' | 'success' | 'failed' | 'canceled' | 'unknown'; + } + >; + cancelAll: FunctionReference< + 'mutation', + 'internal', + { sinceTs?: number }, + Array<{ + batchSize?: number; + cursor?: string | null; + error?: string; + isDone: boolean; + latestEnd?: number; + latestStart: number; + name: string; + next?: Array; + processed: number; + state: 'inProgress' | 'success' | 'failed' | 'canceled' | 'unknown'; + }> + >; + clearAll: FunctionReference<'mutation', 'internal', { before?: number }, null>; + getStatus: FunctionReference< + 'query', + 'internal', + { limit?: number; names?: Array }, + Array<{ + batchSize?: number; + cursor?: string | null; + error?: string; + isDone: boolean; + latestEnd?: number; + latestStart: number; + name: string; + next?: Array; + processed: number; + state: 'inProgress' | 'success' | 'failed' | 'canceled' | 'unknown'; + }> + >; + migrate: FunctionReference< + 'mutation', + 'internal', + { + batchSize?: number; + cursor?: string | null; + dryRun: boolean; + fnHandle: string; + name: string; + next?: Array<{ fnHandle: string; name: string }>; + oneBatchOnly?: boolean; + }, + { + batchSize?: number; + cursor?: string | null; + error?: string; + isDone: boolean; + latestEnd?: number; + latestStart: number; + name: string; + next?: Array; + processed: number; + state: 'inProgress' | 'success' | 'failed' | 'canceled' | 'unknown'; + } + >; + }; + }; }; diff --git a/apps/web/src/convex/_generated/api.js b/apps/web/src/convex/_generated/api.js index 44bf9858..24593c74 100644 --- a/apps/web/src/convex/_generated/api.js +++ b/apps/web/src/convex/_generated/api.js @@ -8,7 +8,7 @@ * @module */ -import { anyApi, componentsGeneric } from "convex/server"; +import { anyApi, componentsGeneric } from 'convex/server'; /** * A utility for referencing Convex functions in your app's API. diff --git a/apps/web/src/convex/_generated/dataModel.d.ts b/apps/web/src/convex/_generated/dataModel.d.ts index f97fd194..5428df6f 100644 --- a/apps/web/src/convex/_generated/dataModel.d.ts +++ b/apps/web/src/convex/_generated/dataModel.d.ts @@ -9,13 +9,13 @@ */ import type { - DataModelFromSchemaDefinition, - DocumentByName, - TableNamesInDataModel, - SystemTableNames, -} from "convex/server"; -import type { GenericId } from "convex/values"; -import schema from "../schema.js"; + DataModelFromSchemaDefinition, + DocumentByName, + TableNamesInDataModel, + SystemTableNames +} from 'convex/server'; +import type { GenericId } from 'convex/values'; +import schema from '../schema.js'; /** * The names of all of your Convex tables. @@ -27,10 +27,7 @@ export type TableNames = TableNamesInDataModel; * * @typeParam TableName - A string literal type of the table name (like "users"). */ -export type Doc = DocumentByName< - DataModel, - TableName ->; +export type Doc = DocumentByName; /** * An identifier for a document in Convex. @@ -45,8 +42,7 @@ export type Doc = DocumentByName< * * @typeParam TableName - A string literal type of the table name (like "users"). */ -export type Id = - GenericId; +export type Id = GenericId; /** * A type describing your Convex data model. diff --git a/apps/web/src/convex/_generated/server.d.ts b/apps/web/src/convex/_generated/server.d.ts index bec05e68..1cc047ef 100644 --- a/apps/web/src/convex/_generated/server.d.ts +++ b/apps/web/src/convex/_generated/server.d.ts @@ -9,17 +9,17 @@ */ import { - ActionBuilder, - HttpActionBuilder, - MutationBuilder, - QueryBuilder, - GenericActionCtx, - GenericMutationCtx, - GenericQueryCtx, - GenericDatabaseReader, - GenericDatabaseWriter, -} from "convex/server"; -import type { DataModel } from "./dataModel.js"; + ActionBuilder, + HttpActionBuilder, + MutationBuilder, + QueryBuilder, + GenericActionCtx, + GenericMutationCtx, + GenericQueryCtx, + GenericDatabaseReader, + GenericDatabaseWriter +} from 'convex/server'; +import type { DataModel } from './dataModel.js'; /** * Define a query in this Convex app's public API. @@ -29,7 +29,7 @@ import type { DataModel } from "./dataModel.js"; * @param func - The query function. It receives a {@link QueryCtx} as its first argument. * @returns The wrapped query. Include this as an `export` to name it and make it accessible. */ -export declare const query: QueryBuilder; +export declare const query: QueryBuilder; /** * Define a query that is only accessible from other Convex functions (but not from the client). @@ -39,7 +39,7 @@ export declare const query: QueryBuilder; * @param func - The query function. It receives a {@link QueryCtx} as its first argument. * @returns The wrapped query. Include this as an `export` to name it and make it accessible. */ -export declare const internalQuery: QueryBuilder; +export declare const internalQuery: QueryBuilder; /** * Define a mutation in this Convex app's public API. @@ -49,7 +49,7 @@ export declare const internalQuery: QueryBuilder; * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. */ -export declare const mutation: MutationBuilder; +export declare const mutation: MutationBuilder; /** * Define a mutation that is only accessible from other Convex functions (but not from the client). @@ -59,7 +59,7 @@ export declare const mutation: MutationBuilder; * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. */ -export declare const internalMutation: MutationBuilder; +export declare const internalMutation: MutationBuilder; /** * Define an action in this Convex app's public API. @@ -72,7 +72,7 @@ export declare const internalMutation: MutationBuilder; * @param func - The action. It receives an {@link ActionCtx} as its first argument. * @returns The wrapped action. Include this as an `export` to name it and make it accessible. */ -export declare const action: ActionBuilder; +export declare const action: ActionBuilder; /** * Define an action that is only accessible from other Convex functions (but not from the client). @@ -80,7 +80,7 @@ export declare const action: ActionBuilder; * @param func - The function. It receives an {@link ActionCtx} as its first argument. * @returns The wrapped function. Include this as an `export` to name it and make it accessible. */ -export declare const internalAction: ActionBuilder; +export declare const internalAction: ActionBuilder; /** * Define an HTTP action. diff --git a/apps/web/src/convex/_generated/server.js b/apps/web/src/convex/_generated/server.js index bf3d25ad..a18aa285 100644 --- a/apps/web/src/convex/_generated/server.js +++ b/apps/web/src/convex/_generated/server.js @@ -9,14 +9,14 @@ */ import { - actionGeneric, - httpActionGeneric, - queryGeneric, - mutationGeneric, - internalActionGeneric, - internalMutationGeneric, - internalQueryGeneric, -} from "convex/server"; + actionGeneric, + httpActionGeneric, + queryGeneric, + mutationGeneric, + internalActionGeneric, + internalMutationGeneric, + internalQueryGeneric +} from 'convex/server'; /** * Define a query in this Convex app's public API. diff --git a/bun.lock b/bun.lock index da42831c..1697419e 100644 --- a/bun.lock +++ b/bun.lock @@ -20,7 +20,7 @@ }, "apps/cli": { "name": "btca", - "version": "2.0.3", + "version": "2.0.4", "bin": { "btca": "bin.js", }, @@ -69,7 +69,7 @@ }, "apps/server": { "name": "btca-server", - "version": "2.0.3", + "version": "2.0.4", "dependencies": { "@ai-sdk/anthropic": "^3.0.23", "@ai-sdk/google": "^3.0.13", diff --git a/package.json b/package.json index a5bcd1e0..fc7d1a3e 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ }, "devDependencies": { "@types/bun": "^1.3.10", - "@typescript/native-preview": "^7.0.0-dev.20260303.1", + "@typescript/native-preview": "^7.0.0-dev.20260305.1", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.5.1", "prettier-plugin-tailwindcss": "^0.7.2",