docorbit 0.1.1 → 0.1.2
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.
- package/README.md +29 -15
- package/dist/apps/cli/src/commands/add.js +2 -2
- package/dist/apps/cli/src/commands/mcp.js +2 -2
- package/dist/apps/cli/src/commands/search.js +2 -2
- package/dist/packages/mcp/src/index.d.ts +1 -1
- package/dist/packages/mcp/src/index.js +1 -1
- package/dist/packages/mcp/src/tools/implementation-context.js +20 -0
- package/dist/packages/mcp/src/tools/index.d.ts +2 -1
- package/dist/packages/mcp/src/tools/index.js +3 -1
- package/dist/packages/mcp/src/tools/ingest-doc.d.ts +6 -0
- package/dist/packages/mcp/src/tools/ingest-doc.js +264 -0
- package/dist/packages/storage/src/db.d.ts +1 -0
- package/dist/packages/storage/src/db.js +73 -6
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -64,19 +64,32 @@ docorbit/
|
|
|
64
64
|
## Getting Started
|
|
65
65
|
|
|
66
66
|
### Requirements
|
|
67
|
-
- **Node.js
|
|
68
|
-
- **
|
|
67
|
+
- **Node.js 22.5.0+** (uses built-in `node:sqlite`)
|
|
68
|
+
- **Zero native dependencies**
|
|
69
69
|
|
|
70
70
|
### Installation
|
|
71
|
-
Clone the repository and link or run directly:
|
|
72
71
|
|
|
72
|
+
#### Global or npx (Recommended)
|
|
73
|
+
```bash
|
|
74
|
+
# Run directly without install
|
|
75
|
+
npx docorbit --help
|
|
76
|
+
|
|
77
|
+
# Or install globally
|
|
78
|
+
npm install -g docorbit
|
|
79
|
+
docorbit --help
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### From Source
|
|
73
83
|
```bash
|
|
74
84
|
# Clone repository
|
|
75
|
-
git clone https://github.com/
|
|
85
|
+
git clone https://github.com/HakashiKatake/docorbit.git
|
|
76
86
|
cd docorbit
|
|
77
87
|
|
|
78
|
-
#
|
|
79
|
-
|
|
88
|
+
# Build distribution bundle
|
|
89
|
+
npm run build
|
|
90
|
+
|
|
91
|
+
# Run CLI
|
|
92
|
+
node bin/docorbit.js --help
|
|
80
93
|
```
|
|
81
94
|
|
|
82
95
|
---
|
|
@@ -425,18 +438,19 @@ node --experimental-strip-types bin/docorbit.js export agents.md --output docs/A
|
|
|
425
438
|
Start DocOrbit as an agent-native MCP server communicating via JSON-RPC 2.0. Coding agents (Claude Code, Cursor, Windsurf, OpenCode) can interact over standard input/output (`stdio`) or Streamable HTTP:
|
|
426
439
|
|
|
427
440
|
```bash
|
|
428
|
-
# Start in Stdio mode (for CLI agents like Claude Code, Cursor,
|
|
429
|
-
|
|
441
|
+
# Start in Stdio mode (for CLI agents like Claude Code, Cursor, Windsurf)
|
|
442
|
+
npx -y docorbit mcp --stdio
|
|
430
443
|
|
|
431
444
|
# Or start in Streamable HTTP mode (supports POST /mcp, GET /sse, GET /health)
|
|
432
|
-
|
|
445
|
+
npx -y docorbit mcp --port 3000 --host 127.0.0.1
|
|
433
446
|
```
|
|
434
447
|
|
|
435
|
-
#### The
|
|
448
|
+
#### The 15 Agent-Native Tools:
|
|
436
449
|
|
|
437
450
|
| Tool Name | Type | Description |
|
|
438
451
|
| :--- | :--- | :--- |
|
|
439
|
-
| `
|
|
452
|
+
| `ingest_doc` | **Ingestion & Loop Starter** | Ingests, crawls, and indexes documentation from any URL or raw content directly into local SQLite store, with optional instant implementation recipe synthesis. |
|
|
453
|
+
| `get_implementation_context` | **High-Level Centerpiece** | Orchestrates task → project/dependency detection → version resolution → intent → retrieval → APIs → examples → pitfalls → recipe → token-budgeted context → provenance → verification hints (supports auto-ingestion from `url`). |
|
|
440
454
|
| `check_api` | Verification | Deterministically checks code against indexed OpenAPI schemas, parameters, required body fields, deprecations, and version contracts. |
|
|
441
455
|
| `diff_docs` | Intelligence | Compares documentation versions/snapshots to detect added, removed, modified, and deprecated endpoints/pitfalls. |
|
|
442
456
|
| `analyze_impact` | Intelligence | Scans workspace project files for breaking changes and deprecated APIs, returning line, snippet, and certainty. |
|
|
@@ -458,8 +472,8 @@ node --experimental-strip-types bin/docorbit.js mcp --port 3000 --host 127.0.0.1
|
|
|
458
472
|
{
|
|
459
473
|
"mcpServers": {
|
|
460
474
|
"docorbit": {
|
|
461
|
-
"command": "
|
|
462
|
-
"args": ["
|
|
475
|
+
"command": "npx",
|
|
476
|
+
"args": ["-y", "docorbit", "mcp", "--stdio"]
|
|
463
477
|
}
|
|
464
478
|
}
|
|
465
479
|
}
|
|
@@ -470,8 +484,8 @@ node --experimental-strip-types bin/docorbit.js mcp --port 3000 --host 127.0.0.1
|
|
|
470
484
|
{
|
|
471
485
|
"mcpServers": {
|
|
472
486
|
"docorbit": {
|
|
473
|
-
"command": "
|
|
474
|
-
"args": ["
|
|
487
|
+
"command": "npx",
|
|
488
|
+
"args": ["-y", "docorbit", "mcp", "--stdio"]
|
|
475
489
|
}
|
|
476
490
|
}
|
|
477
491
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocOrbitDb, DocOrbitRepository } from "../../../../packages/storage/src/index.js";
|
|
1
|
+
import { DocOrbitDb, DocOrbitRepository, resolveDefaultDbPath } from "../../../../packages/storage/src/index.js";
|
|
2
2
|
import { IngestionPipeline } from "../../../../packages/core/src/index.js";
|
|
3
3
|
import { formatIngestionResult } from "../formatters/terminal.js";
|
|
4
4
|
export async function runAddCommand(targetUrl, options = {}) {
|
|
@@ -7,7 +7,7 @@ export async function runAddCommand(targetUrl, options = {}) {
|
|
|
7
7
|
console.error('Usage: docorbit add <url> [--json] [--db <path>]');
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
|
-
const dbPath = options.dbPath
|
|
10
|
+
const dbPath = resolveDefaultDbPath(options.dbPath);
|
|
11
11
|
const db = new DocOrbitDb(dbPath);
|
|
12
12
|
const repository = new DocOrbitRepository(db);
|
|
13
13
|
try {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DocOrbitDb, DocOrbitRepository } from "../../../../packages/storage/src/index.js";
|
|
1
|
+
import { DocOrbitDb, DocOrbitRepository, resolveDefaultDbPath } from "../../../../packages/storage/src/index.js";
|
|
2
2
|
import { WorkspaceResolver } from "../../../../packages/workspace/src/index.js";
|
|
3
3
|
import { McpServer, StdioServerTransport, StreamableHttpTransport, } from "../../../../packages/mcp/src/index.js";
|
|
4
4
|
export async function runMcpCommand(options = {}) {
|
|
5
|
-
const dbPath = options.dbPath || '.docorbit/docorbit.db';
|
|
6
5
|
const projectDir = options.projectDir || process.cwd();
|
|
6
|
+
const dbPath = resolveDefaultDbPath(options.dbPath, projectDir);
|
|
7
7
|
const db = new DocOrbitDb(dbPath);
|
|
8
8
|
const repo = new DocOrbitRepository(db);
|
|
9
9
|
const resolver = new WorkspaceResolver(repo);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocOrbitDb, DocOrbitRepository } from "../../../../packages/storage/src/index.js";
|
|
1
|
+
import { DocOrbitDb, DocOrbitRepository, resolveDefaultDbPath } from "../../../../packages/storage/src/index.js";
|
|
2
2
|
import { RetrievalEngine } from "../../../../packages/retrieval/src/index.js";
|
|
3
3
|
import { formatSearchResults } from "../formatters/terminal.js";
|
|
4
4
|
export async function runSearchCommand(query, options = {}) {
|
|
@@ -7,7 +7,7 @@ export async function runSearchCommand(query, options = {}) {
|
|
|
7
7
|
console.error('Usage: docorbit search "<query>" [--limit <n>] [--type <type>] [--doc-version <ver>] [--project <dir>] [--json] [--db <path>]');
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
|
-
const dbPath = options.dbPath
|
|
10
|
+
const dbPath = resolveDefaultDbPath(options.dbPath, options.projectDir);
|
|
11
11
|
const db = new DocOrbitDb(dbPath);
|
|
12
12
|
const repository = new DocOrbitRepository(db);
|
|
13
13
|
const engine = new RetrievalEngine(repository);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification, JsonRpcErrorObject, ToolInputSchema, McpTool, ToolContentItem, CallToolResult, McpResource, ResourceContent, ReadResourceResult, ServerCapabilities, InitializeResult, } from './types.ts';
|
|
2
2
|
export { JSONRPC_ERRORS } from './types.ts';
|
|
3
3
|
export type { McpContext, McpToolHandler } from './tools/types.ts';
|
|
4
|
-
export { createDefaultTools, SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, } from './tools/index.ts';
|
|
4
|
+
export { createDefaultTools, SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, GetDocumentationMapTool, ExportAgentContextTool, IngestDocTool, } from './tools/index.ts';
|
|
5
5
|
export type { ResourceManagerOptions } from './resources/index.ts';
|
|
6
6
|
export { McpResourceManager } from './resources/index.ts';
|
|
7
7
|
export type { McpServerOptions } from './server.ts';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { JSONRPC_ERRORS } from "./types.js";
|
|
2
|
-
export { createDefaultTools, SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, } from "./tools/index.js";
|
|
2
|
+
export { createDefaultTools, SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, GetDocumentationMapTool, ExportAgentContextTool, IngestDocTool, } from "./tools/index.js";
|
|
3
3
|
export { McpResourceManager } from "./resources/index.js";
|
|
4
4
|
export { McpServer } from "./server.js";
|
|
5
5
|
export { StdioServerTransport } from "./transports/stdio.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IngestionPipeline } from "../../../core/src/index.js";
|
|
1
2
|
export class ImplementationContextTool {
|
|
2
3
|
definition = {
|
|
3
4
|
name: 'get_implementation_context',
|
|
@@ -9,6 +10,10 @@ export class ImplementationContextTool {
|
|
|
9
10
|
type: 'string',
|
|
10
11
|
description: 'The specific coding task or feature to implement (e.g. "Implement Stripe webhook signature verification in Express").',
|
|
11
12
|
},
|
|
13
|
+
url: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Optional documentation target URL. If provided and not yet indexed in DocOrbit, DocOrbit will automatically ingest and index it before compiling context.',
|
|
16
|
+
},
|
|
12
17
|
project: {
|
|
13
18
|
type: 'string',
|
|
14
19
|
description: 'Path to repository workspace root for project-aware dependency detection (default: current directory).',
|
|
@@ -45,7 +50,22 @@ export class ImplementationContextTool {
|
|
|
45
50
|
const tokenBudget = typeof args.tokenBudget === 'number' && args.tokenBudget > 0
|
|
46
51
|
? args.tokenBudget
|
|
47
52
|
: 4000;
|
|
53
|
+
const url = typeof args.url === 'string' ? args.url.trim() : undefined;
|
|
48
54
|
try {
|
|
55
|
+
if (url) {
|
|
56
|
+
const existing = ctx.repo.getSourceByUrl(url);
|
|
57
|
+
if (!existing) {
|
|
58
|
+
try {
|
|
59
|
+
const pipeline = new IngestionPipeline(ctx.repo, {
|
|
60
|
+
crawlerConfig: { maxPages: 20 },
|
|
61
|
+
});
|
|
62
|
+
await pipeline.ingest(url);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Proceed gracefully with available context if crawling fails
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
49
69
|
const result = await ctx.implService.getContext({
|
|
50
70
|
task,
|
|
51
71
|
projectPath,
|
|
@@ -13,6 +13,7 @@ import { DiffDocsTool } from './diff-docs.ts';
|
|
|
13
13
|
import { AnalyzeImpactTool } from './analyze-impact.ts';
|
|
14
14
|
import { GetDocumentationMapTool } from './get-docs-map.ts';
|
|
15
15
|
import { ExportAgentContextTool } from './export-context.ts';
|
|
16
|
+
import { IngestDocTool } from './ingest-doc.ts';
|
|
16
17
|
export * from './types.ts';
|
|
17
|
-
export { SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, ImplementationContextTool, ImplementationContextTool as GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, GetDocumentationMapTool, ExportAgentContextTool, };
|
|
18
|
+
export { SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, ImplementationContextTool, ImplementationContextTool as GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, GetDocumentationMapTool, ExportAgentContextTool, IngestDocTool, };
|
|
18
19
|
export declare function createDefaultTools(): Map<string, McpToolHandler>;
|
|
@@ -12,8 +12,9 @@ import { DiffDocsTool } from "./diff-docs.js";
|
|
|
12
12
|
import { AnalyzeImpactTool } from "./analyze-impact.js";
|
|
13
13
|
import { GetDocumentationMapTool } from "./get-docs-map.js";
|
|
14
14
|
import { ExportAgentContextTool } from "./export-context.js";
|
|
15
|
+
import { IngestDocTool } from "./ingest-doc.js";
|
|
15
16
|
export * from "./types.js";
|
|
16
|
-
export { SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, ImplementationContextTool, ImplementationContextTool as GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, GetDocumentationMapTool, ExportAgentContextTool, };
|
|
17
|
+
export { SearchDocsTool, GetDocTool, FindApiTool, FindExampleTool, FindPitfallTool, FindRecipeTool, GetVersionTool, ListSourcesTool, ImplementationContextTool, ImplementationContextTool as GetImplementationContextTool, CheckApiTool, DiffDocsTool, AnalyzeImpactTool, GetDocumentationMapTool, ExportAgentContextTool, IngestDocTool, };
|
|
17
18
|
export function createDefaultTools() {
|
|
18
19
|
const tools = new Map();
|
|
19
20
|
const list = [
|
|
@@ -31,6 +32,7 @@ export function createDefaultTools() {
|
|
|
31
32
|
new AnalyzeImpactTool(),
|
|
32
33
|
new GetDocumentationMapTool(),
|
|
33
34
|
new ExportAgentContextTool(),
|
|
35
|
+
new IngestDocTool(),
|
|
34
36
|
];
|
|
35
37
|
for (const tool of list) {
|
|
36
38
|
tools.set(tool.definition.name, tool);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CallToolResult, McpTool } from '../types.ts';
|
|
2
|
+
import type { McpContext, McpToolHandler } from './types.ts';
|
|
3
|
+
export declare class IngestDocTool implements McpToolHandler {
|
|
4
|
+
readonly definition: McpTool;
|
|
5
|
+
execute(args: Record<string, unknown>, ctx: McpContext): Promise<CallToolResult>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { IngestionPipeline } from "../../../core/src/index.js";
|
|
2
|
+
import { buildNormalizedPage, slicePageIntoChunks } from "../../../normalizer/src/index.js";
|
|
3
|
+
export class IngestDocTool {
|
|
4
|
+
definition = {
|
|
5
|
+
name: 'ingest_doc',
|
|
6
|
+
description: 'Ingest, crawl, parse, and index authoritative documentation from any URL or raw content directly into DocOrbit. Extracts semantic chunks, OpenAPI endpoints, code examples, and pitfalls. If taskContext is provided, immediately synthesizes and returns an evidence-grounded implementation recipe with exact code and API details.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
url: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'Documentation target URL to crawl and ingest (e.g. "https://nextjs.org/docs" or "https://support.atlassian.com/...").',
|
|
13
|
+
},
|
|
14
|
+
taskContext: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Optional coding task or intent (e.g. "Connect Atlassian Remote MCP" or "Implement Stripe payment element"). If provided, DocOrbit compiles and returns an immediate implementation recipe using the newly ingested docs.',
|
|
17
|
+
},
|
|
18
|
+
maxPages: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
description: 'Maximum number of pages to crawl (default: 20, max: 50).',
|
|
21
|
+
},
|
|
22
|
+
content: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Optional raw markdown/HTML documentation content to index directly without fetching from the web.',
|
|
25
|
+
},
|
|
26
|
+
title: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Optional title when ingesting raw content or overriding page title.',
|
|
29
|
+
},
|
|
30
|
+
allowLocalhost: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
description: 'Allow crawling localhost endpoints for testing (default: false).',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
async execute(args, ctx) {
|
|
38
|
+
const rawUrl = typeof args.url === 'string' ? args.url.trim() : '';
|
|
39
|
+
const rawContent = typeof args.content === 'string' ? args.content.trim() : '';
|
|
40
|
+
const title = typeof args.title === 'string' ? args.title.trim() : undefined;
|
|
41
|
+
const taskContext = typeof args.taskContext === 'string' ? args.taskContext.trim() : undefined;
|
|
42
|
+
const allowLocalhost = Boolean(args.allowLocalhost);
|
|
43
|
+
const maxPages = typeof args.maxPages === 'number' && args.maxPages > 0
|
|
44
|
+
? Math.min(args.maxPages, 50)
|
|
45
|
+
: 20;
|
|
46
|
+
if (!rawUrl && !rawContent) {
|
|
47
|
+
return {
|
|
48
|
+
isError: true,
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: JSON.stringify({
|
|
53
|
+
error: 'Missing required parameter: provide either "url" to crawl documentation from the web or "content" to index raw documentation.',
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
let targetUrl = rawUrl;
|
|
61
|
+
let snapshotId = '';
|
|
62
|
+
let pagesCount = 0;
|
|
63
|
+
let chunksCount = 0;
|
|
64
|
+
let codeExamplesCount = 0;
|
|
65
|
+
let estimatedTokens = 0;
|
|
66
|
+
let durationMs = 0;
|
|
67
|
+
const pagesSummary = [];
|
|
68
|
+
if (rawContent) {
|
|
69
|
+
const startTime = Date.now();
|
|
70
|
+
targetUrl = rawUrl || 'local://direct-content';
|
|
71
|
+
const sourceId = ctx.repo.saveSource({
|
|
72
|
+
url: targetUrl,
|
|
73
|
+
type: 'web',
|
|
74
|
+
discoveredBy: 'direct',
|
|
75
|
+
status: 'valid',
|
|
76
|
+
confidence: 1.0,
|
|
77
|
+
authority: 'official',
|
|
78
|
+
machineReadable: false,
|
|
79
|
+
});
|
|
80
|
+
snapshotId = ctx.repo.createSnapshot(sourceId, {
|
|
81
|
+
targetUrl,
|
|
82
|
+
pageCount: 1,
|
|
83
|
+
ingestedAt: new Date().toISOString(),
|
|
84
|
+
});
|
|
85
|
+
const page = buildNormalizedPage({
|
|
86
|
+
sourceId,
|
|
87
|
+
url: targetUrl,
|
|
88
|
+
rawContent,
|
|
89
|
+
contentType: 'text/markdown',
|
|
90
|
+
sourceUrl: targetUrl,
|
|
91
|
+
targetUrl,
|
|
92
|
+
title: title || 'Direct Content Ingestion',
|
|
93
|
+
discoveredBy: 'direct',
|
|
94
|
+
fetchedAt: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
ctx.repo.savePage(page);
|
|
97
|
+
const slicing = slicePageIntoChunks(page, snapshotId);
|
|
98
|
+
ctx.repo.saveChunks(slicing.chunks, slicing.relationships, slicing.codeSnippets, slicing.symbols);
|
|
99
|
+
pagesCount = 1;
|
|
100
|
+
chunksCount = slicing.chunks.length;
|
|
101
|
+
codeExamplesCount = page.codeExamples.length;
|
|
102
|
+
estimatedTokens = page.estimatedTokens;
|
|
103
|
+
durationMs = Date.now() - startTime;
|
|
104
|
+
pagesSummary.push({
|
|
105
|
+
title: page.title,
|
|
106
|
+
url: page.url,
|
|
107
|
+
tokens: page.estimatedTokens,
|
|
108
|
+
codeBlocks: page.codeExamples.length,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const pipeline = new IngestionPipeline(ctx.repo, {
|
|
113
|
+
allowLocalhostForTesting: allowLocalhost,
|
|
114
|
+
crawlerConfig: {
|
|
115
|
+
maxPages,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const result = await pipeline.ingest(rawUrl);
|
|
119
|
+
targetUrl = result.targetUrl;
|
|
120
|
+
snapshotId = result.snapshotId;
|
|
121
|
+
pagesCount = result.stats.totalPages;
|
|
122
|
+
chunksCount = result.stats.totalChunks;
|
|
123
|
+
codeExamplesCount = result.stats.totalCodeExamples;
|
|
124
|
+
estimatedTokens = result.stats.totalEstimatedTokens;
|
|
125
|
+
durationMs = result.durationMs;
|
|
126
|
+
for (const p of result.pages) {
|
|
127
|
+
pagesSummary.push({
|
|
128
|
+
title: p.title,
|
|
129
|
+
url: p.url,
|
|
130
|
+
tokens: p.estimatedTokens,
|
|
131
|
+
codeBlocks: p.codeExamples.length,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// If user provided a specific task context, compile an immediate implementation context recipe!
|
|
136
|
+
if (taskContext) {
|
|
137
|
+
const implResult = await ctx.implService.getContext({
|
|
138
|
+
task: taskContext,
|
|
139
|
+
projectPath: ctx.projectDir || ctx.workspaceRoot,
|
|
140
|
+
tokenBudget: 4000,
|
|
141
|
+
});
|
|
142
|
+
const lines = [
|
|
143
|
+
`# 🛰️ DocOrbit: Ingested & Context Compiled`,
|
|
144
|
+
`> [!NOTE]`,
|
|
145
|
+
`> Successfully ingested **${targetUrl}** and compiled implementation context for: *"${taskContext}"*`,
|
|
146
|
+
``,
|
|
147
|
+
`### Ingestion Metrics`,
|
|
148
|
+
`- **Pages Ingested**: ${pagesCount}`,
|
|
149
|
+
`- **Chunks Indexed**: ${chunksCount}`,
|
|
150
|
+
`- **Code Examples Extracted**: ${codeExamplesCount}`,
|
|
151
|
+
`- **Estimated Tokens**: ~${estimatedTokens}`,
|
|
152
|
+
`- **Snapshot ID**: \`${snapshotId}\` (${durationMs}ms)`,
|
|
153
|
+
``,
|
|
154
|
+
`---`,
|
|
155
|
+
``,
|
|
156
|
+
implResult.markdown,
|
|
157
|
+
];
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: JSON.stringify({
|
|
163
|
+
markdown: lines.join('\n'),
|
|
164
|
+
data: {
|
|
165
|
+
targetUrl,
|
|
166
|
+
snapshotId,
|
|
167
|
+
taskContext,
|
|
168
|
+
stats: {
|
|
169
|
+
pagesCount,
|
|
170
|
+
chunksCount,
|
|
171
|
+
codeExamplesCount,
|
|
172
|
+
estimatedTokens,
|
|
173
|
+
durationMs,
|
|
174
|
+
},
|
|
175
|
+
implementationContext: implResult,
|
|
176
|
+
},
|
|
177
|
+
}, null, 2),
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Query any extracted APIs, examples, and pitfalls from the new documentation
|
|
183
|
+
const apiEndpoints = ctx.repo.searchApiEndpoints(targetUrl, { limit: 5 });
|
|
184
|
+
const codeExamples = ctx.repo.searchIndexedExamples(targetUrl, { limit: 3 });
|
|
185
|
+
const pitfalls = ctx.repo.searchPitfalls(targetUrl, { limit: 4 });
|
|
186
|
+
const lines = [
|
|
187
|
+
`# 🛰️ DocOrbit: Documentation Ingested Successfully`,
|
|
188
|
+
`> **Target**: \`${targetUrl}\` | **Snapshot**: \`${snapshotId}\` | **Duration**: ${durationMs}ms`,
|
|
189
|
+
``,
|
|
190
|
+
`### Ingestion Statistics`,
|
|
191
|
+
`- **Pages Ingested**: ${pagesCount}`,
|
|
192
|
+
`- **Chunks Indexed**: ${chunksCount}`,
|
|
193
|
+
`- **Code Examples Indexed**: ${codeExamplesCount}`,
|
|
194
|
+
`- **Total Estimated Tokens**: ~${estimatedTokens}`,
|
|
195
|
+
``,
|
|
196
|
+
`### Ingested Pages Directory`,
|
|
197
|
+
];
|
|
198
|
+
for (let i = 0; i < pagesSummary.length; i++) {
|
|
199
|
+
const p = pagesSummary[i];
|
|
200
|
+
lines.push(`${i + 1}. **${p.title}** - \`${p.url}\` (~${p.tokens} tokens, ${p.codeBlocks} code snippets)`);
|
|
201
|
+
}
|
|
202
|
+
if (apiEndpoints.length > 0) {
|
|
203
|
+
lines.push(``, `### Discovered API Endpoints`);
|
|
204
|
+
for (const ep of apiEndpoints) {
|
|
205
|
+
lines.push(`- **\`${ep.method.toUpperCase()} ${ep.path}\`**: ${ep.summary || ep.description || 'No description'}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (codeExamples.length > 0) {
|
|
209
|
+
lines.push(``, `### Extracted Code Patterns`);
|
|
210
|
+
for (const ex of codeExamples) {
|
|
211
|
+
lines.push(`#### ${ex.task} (${ex.language}${ex.framework ? `, ${ex.framework}` : ''})`);
|
|
212
|
+
lines.push('```' + ex.language);
|
|
213
|
+
lines.push(ex.code);
|
|
214
|
+
lines.push('```\n');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (pitfalls.length > 0) {
|
|
218
|
+
lines.push(``, `### Pitfalls & Deprecation Notices`);
|
|
219
|
+
for (const pf of pitfalls) {
|
|
220
|
+
lines.push(`- ⚠️ **[${pf.kind.toUpperCase()}] ${pf.title}**: ${pf.content}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
lines.push(``, `### Recommended Agent Loop Actions`, `- **Compile Recipe**: Call \`get_implementation_context(task: "...")\` to assemble a grounded blueprint with ordered steps and validation criteria.`, `- **Inspect Endpoints**: Call \`find_api(query: "...")\` for parameters, auth, schemas, and error responses.`, `- **Code Examples**: Call \`find_example(task: "...")\` for targeted snippets matching your framework.`, `- **Check Constraints**: Call \`find_pitfall(query: "...")\` to avoid deprecations or runtime traps.`);
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: JSON.stringify({
|
|
229
|
+
markdown: lines.join('\n'),
|
|
230
|
+
data: {
|
|
231
|
+
targetUrl,
|
|
232
|
+
snapshotId,
|
|
233
|
+
stats: {
|
|
234
|
+
pagesCount,
|
|
235
|
+
chunksCount,
|
|
236
|
+
codeExamplesCount,
|
|
237
|
+
estimatedTokens,
|
|
238
|
+
durationMs,
|
|
239
|
+
},
|
|
240
|
+
pages: pagesSummary,
|
|
241
|
+
apiEndpoints,
|
|
242
|
+
codeExamples,
|
|
243
|
+
pitfalls,
|
|
244
|
+
},
|
|
245
|
+
}, null, 2),
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
return {
|
|
252
|
+
isError: true,
|
|
253
|
+
content: [
|
|
254
|
+
{
|
|
255
|
+
type: 'text',
|
|
256
|
+
text: JSON.stringify({
|
|
257
|
+
error: `DocOrbit Ingestion Failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
258
|
+
}),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -1,20 +1,87 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { mkdirSync, existsSync } from 'node:fs';
|
|
4
|
+
import { homedir as getHomedir, tmpdir as getTmpdir } from 'node:os';
|
|
4
5
|
import { SCHEMA_SQL, FTS_SCHEMA_SQL } from "./schema.js";
|
|
6
|
+
export function resolveDefaultDbPath(explicitDbPath, projectDir) {
|
|
7
|
+
if (explicitDbPath && explicitDbPath !== ':memory:') {
|
|
8
|
+
return explicitDbPath;
|
|
9
|
+
}
|
|
10
|
+
if (explicitDbPath === ':memory:') {
|
|
11
|
+
return ':memory:';
|
|
12
|
+
}
|
|
13
|
+
// 1. If projectDir has an existing .docorbit/docorbit.db, prefer it
|
|
14
|
+
if (projectDir) {
|
|
15
|
+
const projectDb = join(projectDir, '.docorbit', 'docorbit.db');
|
|
16
|
+
if (existsSync(projectDb)) {
|
|
17
|
+
return projectDb;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// 2. If current working directory has .docorbit/docorbit.db, prefer it
|
|
21
|
+
try {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
if (cwd && cwd !== '/') {
|
|
24
|
+
const localDb = join(cwd, '.docorbit', 'docorbit.db');
|
|
25
|
+
if (existsSync(localDb)) {
|
|
26
|
+
return localDb;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
// 3. Canonical global user store: ~/.docorbit/docorbit.db
|
|
32
|
+
const userHome = process.env.HOME || process.env.USERPROFILE || getHomedir();
|
|
33
|
+
if (userHome) {
|
|
34
|
+
return join(userHome, '.docorbit', 'docorbit.db');
|
|
35
|
+
}
|
|
36
|
+
return join(getTmpdir(), '.docorbit', 'docorbit.db');
|
|
37
|
+
}
|
|
5
38
|
export class DocOrbitDb {
|
|
6
39
|
db;
|
|
7
40
|
ftsAvailable = false;
|
|
8
41
|
constructor(dbPath = ':memory:') {
|
|
42
|
+
let targetPath = dbPath;
|
|
9
43
|
if (dbPath !== ':memory:') {
|
|
10
|
-
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// When running in environments where cwd is read-only (e.g. root '/' in MCP clients),
|
|
49
|
+
// fallback to user home directory or system tmpdir
|
|
50
|
+
const userHome = process.env.HOME || process.env.USERPROFILE || getHomedir();
|
|
51
|
+
const fallbackDir = userHome ? join(userHome, '.docorbit') : join(getTmpdir(), '.docorbit');
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(fallbackDir, { recursive: true });
|
|
54
|
+
targetPath = join(fallbackDir, 'docorbit.db');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
try {
|
|
58
|
+
const tmpFallback = join(getTmpdir(), '.docorbit');
|
|
59
|
+
mkdirSync(tmpFallback, { recursive: true });
|
|
60
|
+
targetPath = join(tmpFallback, 'docorbit.db');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
targetPath = ':memory:';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
this.db = new DatabaseSync(targetPath);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
this.db = new DatabaseSync(':memory:');
|
|
73
|
+
targetPath = ':memory:';
|
|
11
74
|
}
|
|
12
|
-
this.db = new DatabaseSync(dbPath);
|
|
13
75
|
// Enable foreign keys
|
|
14
76
|
this.db.exec('PRAGMA foreign_keys = ON;');
|
|
15
77
|
// Enable WAL mode for file-based database for concurrent reads
|
|
16
|
-
if (
|
|
17
|
-
|
|
78
|
+
if (targetPath !== ':memory:') {
|
|
79
|
+
try {
|
|
80
|
+
this.db.exec('PRAGMA journal_mode = WAL;');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore if WAL pragma fails on restricted filesystems
|
|
84
|
+
}
|
|
18
85
|
}
|
|
19
86
|
this.migrate();
|
|
20
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docorbit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The Documentation Intelligence Layer for AI Coding Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/packages/core/src/index.js",
|
|
@@ -43,5 +43,8 @@
|
|
|
43
43
|
"context"
|
|
44
44
|
],
|
|
45
45
|
"author": "DocOrbit Team",
|
|
46
|
-
"license": "MIT"
|
|
47
|
-
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"docorbit": "^0.1.1"
|
|
49
|
+
}
|
|
50
|
+
}
|