docorbit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +660 -0
  3. package/apps/cli/bin/docorbit.js +8 -0
  4. package/apps/cli/src/commands/add.ts +44 -0
  5. package/apps/cli/src/commands/api.ts +38 -0
  6. package/apps/cli/src/commands/context.ts +47 -0
  7. package/apps/cli/src/commands/dashboard.ts +55 -0
  8. package/apps/cli/src/commands/diff.ts +30 -0
  9. package/apps/cli/src/commands/evaluate.ts +133 -0
  10. package/apps/cli/src/commands/examples.ts +39 -0
  11. package/apps/cli/src/commands/export.ts +89 -0
  12. package/apps/cli/src/commands/impact.ts +31 -0
  13. package/apps/cli/src/commands/init.ts +69 -0
  14. package/apps/cli/src/commands/inspect.ts +30 -0
  15. package/apps/cli/src/commands/mcp.ts +72 -0
  16. package/apps/cli/src/commands/pitfalls.ts +38 -0
  17. package/apps/cli/src/commands/recipes.ts +35 -0
  18. package/apps/cli/src/commands/search.ts +48 -0
  19. package/apps/cli/src/commands/update.ts +73 -0
  20. package/apps/cli/src/commands/verify.ts +48 -0
  21. package/apps/cli/src/formatters/colors.ts +23 -0
  22. package/apps/cli/src/formatters/inspection.ts +102 -0
  23. package/apps/cli/src/formatters/knowledge.ts +272 -0
  24. package/apps/cli/src/formatters/retrieval.ts +74 -0
  25. package/apps/cli/src/formatters/terminal.ts +6 -0
  26. package/apps/cli/src/formatters/verification.ts +126 -0
  27. package/apps/cli/src/index.ts +409 -0
  28. package/bin/docorbit.js +8 -0
  29. package/package.json +46 -0
  30. package/packages/core/src/dashboard/server.ts +314 -0
  31. package/packages/core/src/dashboard/ui.ts +586 -0
  32. package/packages/core/src/implementation-service.ts +451 -0
  33. package/packages/core/src/index.ts +7 -0
  34. package/packages/core/src/inspector.ts +71 -0
  35. package/packages/core/src/pipeline.ts +331 -0
  36. package/packages/crawler/src/config.ts +12 -0
  37. package/packages/crawler/src/fetcher.ts +185 -0
  38. package/packages/crawler/src/index.ts +2 -0
  39. package/packages/discovery/src/index.ts +31 -0
  40. package/packages/discovery/src/provider.ts +47 -0
  41. package/packages/discovery/src/providers/generic.ts +98 -0
  42. package/packages/discovery/src/providers/github.ts +61 -0
  43. package/packages/discovery/src/providers/llms-txt.ts +73 -0
  44. package/packages/discovery/src/providers/markdown.ts +48 -0
  45. package/packages/discovery/src/providers/openapi.ts +91 -0
  46. package/packages/discovery/src/providers/sitemap.ts +62 -0
  47. package/packages/discovery/src/providers/skill.ts +54 -0
  48. package/packages/discovery/src/ranker.ts +123 -0
  49. package/packages/evaluation/src/dataset.ts +963 -0
  50. package/packages/evaluation/src/index.ts +8 -0
  51. package/packages/evaluation/src/runner.ts +241 -0
  52. package/packages/evaluation/src/strategies/context7-runner.ts +269 -0
  53. package/packages/evaluation/src/strategies/docorbit-runner.ts +228 -0
  54. package/packages/evaluation/src/strategies/firecrawl-runner.ts +172 -0
  55. package/packages/evaluation/src/strategies/web-search-runner.ts +194 -0
  56. package/packages/evaluation/src/types.ts +34 -0
  57. package/packages/evaluation/src/version-matcher.ts +73 -0
  58. package/packages/export/src/agents-md.ts +200 -0
  59. package/packages/export/src/claude-md.ts +141 -0
  60. package/packages/export/src/docs-map.ts +150 -0
  61. package/packages/export/src/index.ts +6 -0
  62. package/packages/export/src/llms-txt.ts +96 -0
  63. package/packages/export/src/service.ts +250 -0
  64. package/packages/export/src/skill-md.ts +128 -0
  65. package/packages/mcp/src/index.ts +46 -0
  66. package/packages/mcp/src/resources/index.ts +189 -0
  67. package/packages/mcp/src/server.ts +278 -0
  68. package/packages/mcp/src/tools/analyze-impact.ts +74 -0
  69. package/packages/mcp/src/tools/check-api.ts +86 -0
  70. package/packages/mcp/src/tools/diff-docs.ts +68 -0
  71. package/packages/mcp/src/tools/export-context.ts +73 -0
  72. package/packages/mcp/src/tools/find-api.ts +99 -0
  73. package/packages/mcp/src/tools/find-example.ts +100 -0
  74. package/packages/mcp/src/tools/find-pitfall.ts +94 -0
  75. package/packages/mcp/src/tools/find-recipe.ts +98 -0
  76. package/packages/mcp/src/tools/get-doc.ts +130 -0
  77. package/packages/mcp/src/tools/get-docs-map.ts +64 -0
  78. package/packages/mcp/src/tools/get-version.ts +118 -0
  79. package/packages/mcp/src/tools/implementation-context.ts +88 -0
  80. package/packages/mcp/src/tools/index.ts +59 -0
  81. package/packages/mcp/src/tools/list-sources.ts +85 -0
  82. package/packages/mcp/src/tools/search-docs.ts +123 -0
  83. package/packages/mcp/src/tools/types.ts +28 -0
  84. package/packages/mcp/src/transports/http.ts +256 -0
  85. package/packages/mcp/src/transports/stdio.ts +105 -0
  86. package/packages/mcp/src/transports/types.ts +6 -0
  87. package/packages/mcp/src/types.ts +102 -0
  88. package/packages/normalizer/src/example-indexer.ts +240 -0
  89. package/packages/normalizer/src/html.ts +253 -0
  90. package/packages/normalizer/src/index.ts +8 -0
  91. package/packages/normalizer/src/llms.ts +83 -0
  92. package/packages/normalizer/src/openapi/endpoint-parser.ts +406 -0
  93. package/packages/normalizer/src/openapi/schema-resolver.ts +111 -0
  94. package/packages/normalizer/src/openapi.ts +2 -0
  95. package/packages/normalizer/src/page.ts +184 -0
  96. package/packages/normalizer/src/pitfall-extractor.ts +190 -0
  97. package/packages/normalizer/src/slicer.ts +455 -0
  98. package/packages/retrieval/src/engine.ts +120 -0
  99. package/packages/retrieval/src/index.ts +7 -0
  100. package/packages/retrieval/src/intent.ts +43 -0
  101. package/packages/retrieval/src/packer.ts +145 -0
  102. package/packages/retrieval/src/recipe-engine.ts +313 -0
  103. package/packages/retrieval/src/scorer.ts +139 -0
  104. package/packages/retrieval/src/weights.ts +31 -0
  105. package/packages/security/src/annotations.ts +112 -0
  106. package/packages/security/src/index.ts +2 -0
  107. package/packages/security/src/ssrf.ts +153 -0
  108. package/packages/shared/src/errors.ts +53 -0
  109. package/packages/shared/src/hashing.ts +23 -0
  110. package/packages/shared/src/index.ts +3 -0
  111. package/packages/shared/src/types.ts +881 -0
  112. package/packages/storage/src/db.ts +72 -0
  113. package/packages/storage/src/index.ts +11 -0
  114. package/packages/storage/src/interfaces.ts +115 -0
  115. package/packages/storage/src/repositories/api-repository.ts +219 -0
  116. package/packages/storage/src/repositories/chunk-repository.ts +316 -0
  117. package/packages/storage/src/repositories/example-repository.ts +206 -0
  118. package/packages/storage/src/repositories/page-repository.ts +205 -0
  119. package/packages/storage/src/repositories/pitfall-repository.ts +188 -0
  120. package/packages/storage/src/repositories/source-repository.ts +205 -0
  121. package/packages/storage/src/repository.ts +256 -0
  122. package/packages/storage/src/schema.ts +269 -0
  123. package/packages/storage/src/search-tokens.ts +28 -0
  124. package/packages/verification/src/diff-engine.ts +258 -0
  125. package/packages/verification/src/extractor.ts +339 -0
  126. package/packages/verification/src/impact-scanner.ts +203 -0
  127. package/packages/verification/src/index.ts +5 -0
  128. package/packages/verification/src/services.ts +238 -0
  129. package/packages/verification/src/verifier.ts +375 -0
  130. package/packages/workspace/src/detector.ts +143 -0
  131. package/packages/workspace/src/ecosystems/cargo.ts +84 -0
  132. package/packages/workspace/src/ecosystems/composer.ts +42 -0
  133. package/packages/workspace/src/ecosystems/go.ts +54 -0
  134. package/packages/workspace/src/ecosystems/index.ts +34 -0
  135. package/packages/workspace/src/ecosystems/maven.ts +34 -0
  136. package/packages/workspace/src/ecosystems/npm.ts +83 -0
  137. package/packages/workspace/src/ecosystems/pub.ts +40 -0
  138. package/packages/workspace/src/ecosystems/pypi.ts +100 -0
  139. package/packages/workspace/src/ecosystems/rubygems.ts +30 -0
  140. package/packages/workspace/src/ecosystems/types.ts +18 -0
  141. package/packages/workspace/src/index.ts +5 -0
  142. package/packages/workspace/src/lockfile.ts +194 -0
  143. package/packages/workspace/src/resolver.ts +234 -0
  144. package/packages/workspace/src/semver.ts +259 -0
@@ -0,0 +1,72 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { dirname } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { SCHEMA_SQL, FTS_SCHEMA_SQL } from './schema.ts';
5
+
6
+ export class DocOrbitDb {
7
+ private db: DatabaseSync;
8
+ private ftsAvailable = false;
9
+
10
+ constructor(dbPath: string = ':memory:') {
11
+ if (dbPath !== ':memory:') {
12
+ mkdirSync(dirname(dbPath), { recursive: true });
13
+ }
14
+
15
+ this.db = new DatabaseSync(dbPath);
16
+
17
+ // Enable foreign keys
18
+ this.db.exec('PRAGMA foreign_keys = ON;');
19
+
20
+ // Enable WAL mode for file-based database for concurrent reads
21
+ if (dbPath !== ':memory:') {
22
+ this.db.exec('PRAGMA journal_mode = WAL;');
23
+ }
24
+
25
+ this.migrate();
26
+ }
27
+
28
+ private migrate(): void {
29
+ // Base relational tables
30
+ this.db.exec(SCHEMA_SQL);
31
+
32
+ // Schema evolution migrations
33
+ try {
34
+ this.db.exec('ALTER TABLE pages ADD COLUMN provenance_json TEXT;');
35
+ } catch {
36
+ // Column already exists
37
+ }
38
+
39
+ try {
40
+ this.db.exec('ALTER TABLE snapshots ADD COLUMN doc_version TEXT;');
41
+ } catch {
42
+ // Column already exists
43
+ }
44
+
45
+ try {
46
+ this.db.exec('ALTER TABLE chunks ADD COLUMN doc_version TEXT;');
47
+ } catch {
48
+ // Column already exists
49
+ }
50
+
51
+ // Test and enable FTS5 virtual table
52
+ try {
53
+ this.db.exec(FTS_SCHEMA_SQL);
54
+ this.ftsAvailable = true;
55
+ } catch {
56
+ this.ftsAvailable = false;
57
+ }
58
+ }
59
+
60
+ isFtsAvailable(): boolean {
61
+ return this.ftsAvailable;
62
+ }
63
+
64
+ getRawDb(): DatabaseSync {
65
+ return this.db;
66
+ }
67
+
68
+ close(): void {
69
+ this.db.close();
70
+ }
71
+ }
72
+
@@ -0,0 +1,11 @@
1
+ export * from './db.ts';
2
+ export * from './schema.ts';
3
+ export * from './repository.ts';
4
+ export type * from './interfaces.ts';
5
+ export * from './search-tokens.ts';
6
+ export * from './repositories/source-repository.ts';
7
+ export * from './repositories/page-repository.ts';
8
+ export * from './repositories/chunk-repository.ts';
9
+ export * from './repositories/api-repository.ts';
10
+ export * from './repositories/example-repository.ts';
11
+ export * from './repositories/pitfall-repository.ts';
@@ -0,0 +1,115 @@
1
+ import type {
2
+ DiscoveredSource,
3
+ NormalizedPage,
4
+ DocumentChunk,
5
+ ChunkCode,
6
+ SymbolReference,
7
+ ChunkRelationship,
8
+ ChunkType,
9
+ ApiEndpoint,
10
+ IndexedExample,
11
+ Pitfall,
12
+ PitfallKind,
13
+ } from '../../shared/src/index.ts';
14
+
15
+ export interface Snapshot {
16
+ id: string;
17
+ sourceId: string;
18
+ hash: string;
19
+ pageCount: number;
20
+ docVersion?: string;
21
+ createdAt: string;
22
+ }
23
+
24
+ export interface ISourceRepository {
25
+ saveSource(source: DiscoveredSource): string;
26
+ getSource(id: string): DiscoveredSource | null;
27
+ getSourceByUrl(url: string): DiscoveredSource | null;
28
+ listSources(): DiscoveredSource[];
29
+ createSnapshot(sourceId: string, metadata?: Record<string, unknown>, docVersion?: string): string;
30
+ saveSnapshot(snapshot: Snapshot): void;
31
+ getSnapshot(id: string): Snapshot | null;
32
+ getLatestSnapshot(sourceId: string): Snapshot | null;
33
+ listSnapshots(sourceId: string): Snapshot[];
34
+ linkSnapshotPages(snapshotId: string, pageIds: string[]): void;
35
+ getSnapshotPages(snapshotId: string): NormalizedPage[];
36
+ }
37
+
38
+ export interface IPageRepository {
39
+ savePage(page: NormalizedPage): void;
40
+ getPage(id: string): NormalizedPage | null;
41
+ getPageByUrl(normalizedUrl: string): NormalizedPage | null;
42
+ listPages(limit?: number, offset?: number): NormalizedPage[];
43
+ countPages(sourceId?: string): number;
44
+ savePageLinks(pageId: string, targetUrls: string[]): void;
45
+ getPageLinks(pageId: string): string[];
46
+ searchPagesFts(query: string, limit?: number): NormalizedPage[];
47
+ }
48
+
49
+ export interface IChunkRepository {
50
+ saveChunks(
51
+ chunks: DocumentChunk[],
52
+ relationships?: ChunkRelationship[],
53
+ codeSnippets?: ChunkCode[],
54
+ symbols?: SymbolReference[]
55
+ ): void;
56
+ getChunk(id: string): DocumentChunk | null;
57
+ getChunksByPage(pageId: string): DocumentChunk[];
58
+ getChunksBySnapshot(snapshotId: string): DocumentChunk[];
59
+ getChunksByVersion(docVersion: string): DocumentChunk[];
60
+ getChunkCode(chunkId: string): ChunkCode[];
61
+ getChunkSymbols(chunkId: string): SymbolReference[];
62
+ getChunkRelationships(chunkId: string): ChunkRelationship[];
63
+ countChunks(snapshotId?: string): number;
64
+ searchChunksFts(
65
+ query: string,
66
+ options?: {
67
+ limit?: number;
68
+ snapshotId?: string;
69
+ chunkType?: ChunkType;
70
+ docVersion?: string;
71
+ }
72
+ ): Array<{
73
+ chunk: DocumentChunk;
74
+ ftsRank: number;
75
+ symbols: SymbolReference[];
76
+ codeSnippets: ChunkCode[];
77
+ }>;
78
+ }
79
+
80
+ export interface IApiRepository {
81
+ saveApiEndpoints(endpoints: ApiEndpoint[]): void;
82
+ getApiEndpoint(id: string): ApiEndpoint | null;
83
+ getApiEndpointsBySnapshot(snapshotId: string): ApiEndpoint[];
84
+ searchApiEndpoints(
85
+ query: string,
86
+ options?: { method?: string; docVersion?: string; limit?: number }
87
+ ): ApiEndpoint[];
88
+ }
89
+
90
+ export interface IExampleRepository {
91
+ saveIndexedExamples(examples: IndexedExample[]): void;
92
+ getIndexedExamplesBySnapshot(snapshotId: string): IndexedExample[];
93
+ searchIndexedExamples(
94
+ query: string,
95
+ options?: {
96
+ language?: string;
97
+ framework?: string;
98
+ docVersion?: string;
99
+ limit?: number;
100
+ }
101
+ ): IndexedExample[];
102
+ }
103
+
104
+ export interface IPitfallRepository {
105
+ savePitfalls(pitfalls: Pitfall[]): void;
106
+ getPitfallsBySnapshot(snapshotId: string): Pitfall[];
107
+ searchPitfalls(
108
+ query: string,
109
+ options?: {
110
+ kind?: PitfallKind;
111
+ docVersion?: string;
112
+ limit?: number;
113
+ }
114
+ ): Pitfall[];
115
+ }
@@ -0,0 +1,219 @@
1
+ import type { ApiEndpoint } from '../../../shared/src/index.ts';
2
+ import type { DocOrbitDb } from '../db.ts';
3
+ import type { IApiRepository } from '../interfaces.ts';
4
+ import { extractSearchTokens } from '../search-tokens.ts';
5
+
6
+ export class ApiRepository implements IApiRepository {
7
+ private db: DocOrbitDb;
8
+
9
+ constructor(db: DocOrbitDb) {
10
+ this.db = db;
11
+ }
12
+
13
+ saveApiEndpoints(endpoints: ApiEndpoint[]): void {
14
+ if (endpoints.length === 0) return;
15
+ const raw = this.db.getRawDb();
16
+
17
+ const insertEp = raw.prepare(`
18
+ INSERT INTO api_endpoints (
19
+ id, page_id, snapshot_id, method, path, summary, description,
20
+ operation_id, parameters_json, request_schema_json, response_schema_json,
21
+ auth_json, errors_json, pagination_json, deprecated, doc_version,
22
+ provenance_json, created_at
23
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
24
+ ON CONFLICT(id) DO UPDATE SET
25
+ method = excluded.method,
26
+ path = excluded.path,
27
+ summary = excluded.summary,
28
+ description = excluded.description,
29
+ operation_id = excluded.operation_id,
30
+ parameters_json = excluded.parameters_json,
31
+ request_schema_json = excluded.request_schema_json,
32
+ response_schema_json = excluded.response_schema_json,
33
+ auth_json = excluded.auth_json,
34
+ errors_json = excluded.errors_json,
35
+ pagination_json = excluded.pagination_json,
36
+ deprecated = excluded.deprecated,
37
+ doc_version = excluded.doc_version,
38
+ provenance_json = excluded.provenance_json
39
+ `);
40
+
41
+ const insertFts = this.db.isFtsAvailable()
42
+ ? raw.prepare(`
43
+ INSERT INTO api_endpoints_fts (endpoint_id, method, path, summary, description, parameters)
44
+ VALUES (?, ?, ?, ?, ?, ?)
45
+ `)
46
+ : null;
47
+
48
+ const deleteFts = this.db.isFtsAvailable()
49
+ ? raw.prepare('DELETE FROM api_endpoints_fts WHERE endpoint_id = ?')
50
+ : null;
51
+
52
+ raw.exec('BEGIN TRANSACTION;');
53
+ try {
54
+ for (const ep of endpoints) {
55
+ insertEp.run(
56
+ ep.id,
57
+ ep.pageId,
58
+ ep.snapshotId || 'snap_default',
59
+ ep.method,
60
+ ep.path,
61
+ ep.summary || null,
62
+ ep.description || null,
63
+ ep.operationId || null,
64
+ JSON.stringify(ep.parameters || []),
65
+ ep.requestSchema ? JSON.stringify(ep.requestSchema) : null,
66
+ ep.responseSchema ? JSON.stringify(ep.responseSchema) : null,
67
+ ep.auth ? JSON.stringify(ep.auth) : '[]',
68
+ ep.errors ? JSON.stringify(ep.errors) : '[]',
69
+ ep.pagination ? JSON.stringify(ep.pagination) : null,
70
+ ep.deprecated ? 1 : 0,
71
+ ep.docVersion || null,
72
+ ep.provenance ? JSON.stringify(ep.provenance) : null,
73
+ ep.createdAt || new Date().toISOString()
74
+ );
75
+
76
+ if (insertFts && deleteFts) {
77
+ deleteFts.run(ep.id);
78
+ const paramNames = (ep.parameters || []).map(p => p.name).join(' ');
79
+ insertFts.run(
80
+ ep.id,
81
+ ep.method,
82
+ ep.path,
83
+ ep.summary || '',
84
+ ep.description || '',
85
+ paramNames
86
+ );
87
+ }
88
+ }
89
+ raw.exec('COMMIT;');
90
+ } catch (err) {
91
+ raw.exec('ROLLBACK;');
92
+ throw err;
93
+ }
94
+ }
95
+
96
+ getApiEndpoint(id: string): ApiEndpoint | null {
97
+ const raw = this.db.getRawDb();
98
+ const row = raw.prepare('SELECT * FROM api_endpoints WHERE id = ?').get(id) as Record<string, unknown> | undefined;
99
+ return row ? this.hydrateApiEndpoint(row) : null;
100
+ }
101
+
102
+ getApiEndpointsBySnapshot(snapshotId: string): ApiEndpoint[] {
103
+ const raw = this.db.getRawDb();
104
+ const rows = raw.prepare('SELECT * FROM api_endpoints WHERE snapshot_id = ? ORDER BY path, method').all(snapshotId) as Array<Record<string, unknown>>;
105
+ return rows.map(r => this.hydrateApiEndpoint(r));
106
+ }
107
+
108
+ searchApiEndpoints(
109
+ query: string,
110
+ options: { method?: string; docVersion?: string; limit?: number } = {}
111
+ ): ApiEndpoint[] {
112
+ const raw = this.db.getRawDb();
113
+ const limit = options.limit || 20;
114
+ const cleanQuery = query.trim();
115
+
116
+ // Check if query specifies method at start (e.g. "POST /v1/...")
117
+ let effectiveMethod = options.method?.toLowerCase();
118
+ let term = cleanQuery;
119
+ const methodMatch = cleanQuery.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(.*)$/i);
120
+ if (methodMatch) {
121
+ effectiveMethod = methodMatch[1].toLowerCase();
122
+ term = methodMatch[2].trim();
123
+ }
124
+
125
+ // Try FTS if available and term exists
126
+ if (this.db.isFtsAvailable() && term.length > 0) {
127
+ const cleanTokens = extractSearchTokens(term);
128
+
129
+ if (cleanTokens.length > 0) {
130
+ const sanitizedFts = cleanTokens
131
+ .map(t => t.length >= 4 ? `"${t.replace(/"/g, '""')}"*` : `"${t.replace(/"/g, '""')}"`)
132
+ .join(' OR ');
133
+ try {
134
+ let sql = `
135
+ SELECT ep.*, fts.rank as fts_rank
136
+ FROM api_endpoints ep
137
+ JOIN api_endpoints_fts fts ON ep.id = fts.endpoint_id
138
+ WHERE api_endpoints_fts MATCH ?
139
+ `;
140
+ const params: unknown[] = [sanitizedFts];
141
+
142
+ if (effectiveMethod) {
143
+ sql += ' AND lower(ep.method) = ?';
144
+ params.push(effectiveMethod);
145
+ }
146
+ if (options.docVersion) {
147
+ sql += ' AND (ep.doc_version = ? OR ep.doc_version IS NULL)';
148
+ params.push(options.docVersion);
149
+ }
150
+
151
+ sql += ' ORDER BY fts.rank LIMIT ?';
152
+ params.push(limit);
153
+
154
+ const rows = raw.prepare(sql).all(...params) as Array<Record<string, unknown>>;
155
+ if (rows.length > 0) {
156
+ return rows.map(r => this.hydrateApiEndpoint(r));
157
+ }
158
+ } catch {
159
+ // Fall through to LIKE search
160
+ }
161
+ }
162
+ }
163
+
164
+ // LIKE fallback search
165
+ let sql = 'SELECT * FROM api_endpoints WHERE 1=1';
166
+ const params: unknown[] = [];
167
+
168
+ if (effectiveMethod) {
169
+ sql += ' AND lower(method) = ?';
170
+ params.push(effectiveMethod);
171
+ }
172
+ if (options.docVersion) {
173
+ sql += ' AND (doc_version = ? OR doc_version IS NULL)';
174
+ params.push(options.docVersion);
175
+ }
176
+ if (term.length > 0) {
177
+ const tokens = extractSearchTokens(term);
178
+ if (tokens.length > 0) {
179
+ const tokenClauses = tokens.map(() => '(path LIKE ? OR summary LIKE ? OR description LIKE ? OR parameters_json LIKE ?)');
180
+ sql += ` AND (${tokenClauses.join(' OR ')})`;
181
+ for (const t of tokens) {
182
+ const lp = `%${t}%`;
183
+ params.push(lp, lp, lp, lp);
184
+ }
185
+ } else {
186
+ return [];
187
+ }
188
+ }
189
+
190
+ sql += ' ORDER BY path, method LIMIT ?';
191
+ params.push(limit);
192
+
193
+ const rows = raw.prepare(sql).all(...params) as Array<Record<string, unknown>>;
194
+ return rows.map(r => this.hydrateApiEndpoint(r));
195
+ }
196
+
197
+ private hydrateApiEndpoint(row: Record<string, unknown>): ApiEndpoint {
198
+ return {
199
+ id: String(row.id),
200
+ pageId: String(row.page_id),
201
+ snapshotId: String(row.snapshot_id),
202
+ method: String(row.method) as ApiEndpoint['method'],
203
+ path: String(row.path),
204
+ summary: row.summary ? String(row.summary) : undefined,
205
+ description: row.description ? String(row.description) : undefined,
206
+ operationId: row.operation_id ? String(row.operation_id) : undefined,
207
+ parameters: row.parameters_json ? JSON.parse(String(row.parameters_json)) : [],
208
+ requestSchema: row.request_schema_json ? JSON.parse(String(row.request_schema_json)) : undefined,
209
+ responseSchema: row.response_schema_json ? JSON.parse(String(row.response_schema_json)) : undefined,
210
+ auth: row.auth_json ? JSON.parse(String(row.auth_json)) : [],
211
+ errors: row.errors_json ? JSON.parse(String(row.errors_json)) : [],
212
+ pagination: row.pagination_json ? JSON.parse(String(row.pagination_json)) : undefined,
213
+ deprecated: Number(row.deprecated) === 1,
214
+ docVersion: row.doc_version ? String(row.doc_version) : undefined,
215
+ provenance: row.provenance_json ? JSON.parse(String(row.provenance_json)) : undefined,
216
+ createdAt: String(row.created_at),
217
+ };
218
+ }
219
+ }