@agentskit/doc-bridge 1.2.4 → 1.3.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 (43) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +6 -0
  3. package/PRIVACY.md +38 -0
  4. package/README.md +30 -3
  5. package/SECURITY.md +7 -2
  6. package/action.yml +1 -1
  7. package/dist/cli/program.js +400 -156
  8. package/dist/cli/program.js.map +1 -1
  9. package/dist/config/index.d.ts +1 -1
  10. package/dist/config/index.js +3 -1
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/{index-DGI9TBLE.d.ts → index-DAeq_OIi.d.ts} +22 -22
  13. package/dist/index.d.ts +47 -12
  14. package/dist/index.js +376 -131
  15. package/dist/index.js.map +1 -1
  16. package/docs/POSITIONING.md +1 -1
  17. package/docs/examples.md +4 -0
  18. package/docs/getting-started.md +1 -1
  19. package/docs/landing/index.html +1 -1
  20. package/docs/mcp.md +16 -0
  21. package/docs/qa/vitepress-starlight-adapters.md +19 -0
  22. package/docs/spec/config-v1.md +33 -2
  23. package/examples/nextra-only.config.ts +17 -0
  24. package/examples/nx-monorepo.config.ts +11 -0
  25. package/examples/starlight-only.config.ts +17 -0
  26. package/examples/vitepress-only.config.ts +19 -0
  27. package/mcpb/.mcpbignore +8 -0
  28. package/mcpb/icon.png +0 -0
  29. package/mcpb/manifest.json +96 -0
  30. package/package.json +14 -11
  31. package/src/config/schema.ts +3 -1
  32. package/src/index-builder/build-handoffs.ts +2 -0
  33. package/src/index-builder/build-index.ts +7 -1
  34. package/src/index-builder/human-adapters/index.ts +6 -0
  35. package/src/index-builder/human-adapters/nextra.ts +43 -0
  36. package/src/index-builder/human-adapters/starlight.ts +40 -0
  37. package/src/index-builder/human-adapters/vitepress.ts +43 -0
  38. package/src/index-builder/plugins/nx.ts +161 -0
  39. package/src/index-builder/plugins/pnpm-monorepo.ts +1 -0
  40. package/src/index-builder/watch-index.ts +11 -2
  41. package/src/index.ts +1 -0
  42. package/src/mcp/server.ts +58 -26
  43. package/src/version.ts +1 -1
@@ -70,7 +70,7 @@ Engineering teams with real ownership (monorepos first). Secondary: solo libs, i
70
70
 
71
71
  ```
72
72
  required: index, CLI, MCP handoff tools, gate presets
73
- optional: fumadocs | docusaurus | plain-markdown plugins
73
+ optional: fumadocs | docusaurus | vitepress | starlight | nextra | plain-markdown plugins
74
74
  optional: memory ingest → promote
75
75
  optional: @agentskit/rag + ink chat (intelligence.*)
76
76
  optional: Playbook / Registry federation
package/docs/examples.md CHANGED
@@ -11,8 +11,12 @@ Config sketches under [`examples/`](../examples/):
11
11
  |------|---------|
12
12
  | `minimal-plain-markdown.config.ts` | Solo markdown, Layer 0 only |
13
13
  | `pnpm-monorepo.config.ts` | Workspace discovery + ownership |
14
+ | `nx-monorepo.config.ts` | Static Nx project discovery + inferred checks |
14
15
  | `fumadocs-only.config.ts` | Human bridge via Fumadocs |
15
16
  | `docusaurus-only.config.ts` | Human bridge via Docusaurus |
17
+ | `vitepress-only.config.ts` | Human bridge via VitePress |
18
+ | `starlight-only.config.ts` | Human bridge via Astro Starlight |
19
+ | `nextra-only.config.ts` | Human bridge via Nextra |
16
20
  | `fumadocs-with-chat.config.ts` | Standard + intelligence (AgentsKit peers) |
17
21
  | `docusaurus-with-memory.config.ts` | Assisted memory promotion path |
18
22
 
@@ -105,7 +105,7 @@ Tools: `handoff.resolve`, `doc.search`, `doc.get`, `gate.status`, …
105
105
  ## Human ↔ agent bridge
106
106
 
107
107
  ```bash
108
- # After configuring corpus.human (fumadocs | docusaurus | plain-markdown)
108
+ # After configuring corpus.human (fumadocs | docusaurus | vitepress | starlight | nextra | plain-markdown)
109
109
  ak-docs index
110
110
  ak-docs query package <id> --agent # includes humanDoc when linked
111
111
  ak-docs gate run human-guide-links
@@ -272,7 +272,7 @@
272
272
  <tr><td>CLI</td><td>Query ownership, inspect handoffs, run doctor, search docs.</td><td><code>ak-docs query package auth --agent</code></td></tr>
273
273
  <tr><td>MCP</td><td>Lets Cursor, Claude Code, and Codex-style agents resolve before editing.</td><td><code>ak-docs mcp install --cursor</code></td></tr>
274
274
  <tr><td>CI</td><td>Blocks stale indexes and broken human-doc bridges.</td><td><code>ak-docs index && ak-docs gate run</code></td></tr>
275
- <tr><td>Adapters</td><td>Connect Fumadocs, Docusaurus, plain markdown, and pnpm workspaces.</td><td><code>fumadocs</code> · <code>docusaurus</code> · <code>plain-markdown</code></td></tr>
275
+ <tr><td>Adapters</td><td>Connect Fumadocs, Docusaurus, VitePress, Starlight, Nextra, plain markdown, and pnpm workspaces.</td><td><code>fumadocs</code> · <code>docusaurus</code> · <code>vitepress</code> · <code>starlight</code> · <code>nextra</code> · <code>plain-markdown</code></td></tr>
276
276
  <tr><td>Memory pipeline</td><td>Classifies local agent memory and drafts documentation updates.</td><td><code>ak-docs memory promote --pr</code></td></tr>
277
277
  <tr><td>Optional RAG/chat</td><td>Adds handoff-first chat when you install AgentsKit peers.</td><td><code>ak-docs rag ingest && ak-docs chat</code></td></tr>
278
278
  </tbody>
package/docs/mcp.md CHANGED
@@ -43,6 +43,19 @@ Or with a global/local bin:
43
43
 
44
44
  Run from the repo root (or pass config discovery that resolves to it). Always `ak-docs index` after doc changes (or gate in CI).
45
45
 
46
+ ## Claude Desktop MCP Bundle
47
+
48
+ Maintainers can build the local desktop extension from a clean checkout:
49
+
50
+ ```bash
51
+ pnpm install --frozen-lockfile
52
+ pnpm mcpb:pack
53
+ ```
54
+
55
+ The bundle asks the user to select the repository's `doc-bridge.config.json` and uses that file's directory as the project boundary. It contains a self-contained MCP runtime rather than the optional RAG, chat, or model-provider packages. The build validates the manifest, checks the archive inventory, and exercises all eight tools from the staged runtime.
56
+
57
+ The stdio server accepts the newline-delimited JSON transport used by current MCP clients and the legacy `Content-Length` framing used by older integrations. Responses use the same framing as each request.
58
+
46
59
  ## Tools
47
60
 
48
61
  | Tool | Purpose |
@@ -53,6 +66,9 @@ Run from the repo root (or pass config discovery that resolves to it). Always `a
53
66
  | `gate.status` | Freshness / configured gates |
54
67
  | `retriever.query` | Local retriever chunks |
55
68
  | `memory.classify` / `memory.promoteDraft` | Memory pipeline |
69
+ | `registry.topology` | Static curator and delegate topology |
70
+
71
+ Every tool is annotated read-only. None of these MCP calls writes project files or publishes a memory promotion.
56
72
 
57
73
  ## Agent guidance (paste into AGENTS.md)
58
74
 
@@ -0,0 +1,19 @@
1
+ # VitePress and Starlight adapter QA plan
2
+
3
+ This plan records the local acceptance criteria for the first-party VitePress and Astro Starlight human-documentation adapters.
4
+
5
+ ## Contract checks
6
+
7
+ - A VitePress corpus scans Markdown and MDX below its configured documentation root, maps `index` files to their directory route, follows the framework's default `.html` routes or optional `cleanUrls`, applies declarative `srcExclude` globs, respects an optional `urlPrefix`, and keeps Doc Bridge join metadata (`package`, `module`, or `id`) stable.
8
+ - A Starlight corpus scans Markdown and MDX below its configured content root, uses Astro's optional `slug` frontmatter for the public route, maps index pages correctly, respects an optional `urlPrefix`, and keeps Doc Bridge join metadata stable.
9
+ - Both adapters reject roots outside the project through the shared bounded, containment-aware scanner.
10
+ - Neither adapter executes framework configuration or user JavaScript.
11
+ - Existing Fumadocs, Docusaurus, and plain-Markdown behavior remains unchanged.
12
+
13
+ ## Local verification
14
+
15
+ 1. Add focused fixtures for default routes, index routes, metadata overrides, URL prefixes, and nested agent-corpus exclusion.
16
+ 2. Run `pnpm vitest run tests/human-adapters.test.ts tests/schemas.test.ts`.
17
+ 3. Run `pnpm typecheck` and `pnpm build` to validate the public configuration type and emitted declarations.
18
+ 4. Run the complete `pnpm test` suite to detect regressions.
19
+ 5. Run `git diff --check` and inspect the final diff for scope and generated-file noise.
@@ -129,10 +129,34 @@ type HumanCorpusPluginId =
129
129
  | 'fumadocs' // markdown scan with index routes, (group) slugs, pages allowlists
130
130
  | 'docusaurus' // markdown scan with id/slug frontmatter + static sidebars.js
131
131
  | 'mkdocs' // planned
132
- | 'vitepress' // planned
132
+ | 'vitepress' // file routes, srcDir/docsDir, optional cleanUrls
133
+ | 'starlight' // Astro content routes, slug and draft frontmatter
134
+ | 'nextra' // content-directory routes and contentDirBasePath
133
135
  | 'custom' // path to user plugin module
134
136
  ```
135
137
 
138
+ VitePress options accept `docsDir` (also `root` or `srcDir`), `urlPrefix`,
139
+ `cleanUrls`, and up to 64 declarative `srcExclude` glob patterns. Doc Bridge
140
+ uses `.html` routes unless `cleanUrls: true`, matching
141
+ VitePress routing without loading or executing `.vitepress/config.*`. Route
142
+ rewrites must therefore be reflected in the configured corpus or URL prefix.
143
+
144
+ Starlight options accept `contentDir` (also `docsDir` or `root`) and
145
+ `urlPrefix`. The adapter reads the standard `slug` and `draft` frontmatter,
146
+ follows the default filename sluggifier, excludes underscore-prefixed
147
+ partials, and never executes `astro.config.*`. Sites using a custom
148
+ `docsLoader({ generateId })` should add explicit `slug` frontmatter so Doc
149
+ Bridge can resolve the same public route without executing project code.
150
+
151
+ Nextra options accept `contentDir` (also `docsDir` or `root`), `urlPrefix`,
152
+ and `contentDirBasePath`. The adapter maps `index.md` and `index.mdx` to the
153
+ containing route and uses Doc Bridge `package`, `module`, or `id` frontmatter
154
+ as the join key. `urlPrefix` takes precedence over `contentDirBasePath`. Point
155
+ `contentDir` at either `content` or `src/content`; Doc Bridge never imports or
156
+ executes `next.config.*`, `_meta.*`, themes, or other project code. This
157
+ adapter targets Nextra's content-directory convention; app-router `page.mdx`
158
+ trees are not inferred by this plugin.
159
+
136
160
  ### Bridge to agent docs
137
161
 
138
162
  When `corpus.human` is set, plugins MUST:
@@ -199,7 +223,7 @@ Without `routing`, handoffs are inferred from agent corpus links only. Monorepo
199
223
 
200
224
  ```ts
201
225
  type RoutingConfig = {
202
- plugin?: 'pnpm-monorepo' | 'npm-workspaces' | 'yarn-workspaces' | 'custom'
226
+ plugin?: 'pnpm-monorepo' | 'npm-workspaces' | 'yarn-workspaces' | 'nx' | 'pattern-files' | 'custom'
203
227
 
204
228
  options?: {
205
229
  /** Workspace globs; default from package manager */
@@ -240,6 +264,13 @@ type ChangeRouteEntry = {
240
264
  }
241
265
  ```
242
266
 
267
+ `routing.plugin: 'nx'` reads `project.json` and package manifests with an `nx`
268
+ object. It does not load Nx plugins or execute workspace code. Project roots must
269
+ resolve inside the configured project root. Declared `test` targets become Nx test
270
+ checks; non-minimal gate presets also include declared `lint` targets. Explicit
271
+ `routing.options.ownership` remains authoritative. Targets created only at runtime
272
+ by Nx plugins are intentionally not inferred by this read-only adapter.
273
+
243
274
  ### Join keys (agent ↔ human ↔ ownership)
244
275
 
245
276
  | Entity | Primary key | Human plugin maps via |
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from '@agentskit/doc-bridge'
2
+
3
+ /** Nextra bridge only: no chat, no memory, no provider key. */
4
+ export default defineConfig({
5
+ schemaVersion: 1,
6
+ corpus: {
7
+ agent: { root: 'docs/for-agents' },
8
+ human: {
9
+ plugin: 'nextra',
10
+ options: {
11
+ contentDir: 'content',
12
+ contentDirBasePath: '/docs',
13
+ },
14
+ },
15
+ },
16
+ gates: { preset: 'standard' },
17
+ })
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from '@agentskit/doc-bridge'
2
+
3
+ /** Nx — infer project ownership and available test/lint checks without executing Nx */
4
+ export default defineConfig({
5
+ schemaVersion: 1,
6
+ corpus: {
7
+ agent: { root: 'docs/for-agents' },
8
+ },
9
+ routing: { plugin: 'nx' },
10
+ gates: { preset: 'standard' },
11
+ })
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from '@agentskit/doc-bridge'
2
+
3
+ /** Starlight bridge only: no chat, no memory, no provider key. */
4
+ export default defineConfig({
5
+ schemaVersion: 1,
6
+ corpus: {
7
+ agent: { root: 'docs/for-agents' },
8
+ human: {
9
+ plugin: 'starlight',
10
+ options: {
11
+ contentDir: 'src/content/docs',
12
+ urlPrefix: '/docs',
13
+ },
14
+ },
15
+ },
16
+ gates: { preset: 'standard' },
17
+ })
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from '@agentskit/doc-bridge'
2
+
3
+ /** VitePress bridge only: no chat, no memory, no provider key. */
4
+ export default defineConfig({
5
+ schemaVersion: 1,
6
+ corpus: {
7
+ agent: { root: 'docs/for-agents' },
8
+ human: {
9
+ plugin: 'vitepress',
10
+ options: {
11
+ docsDir: 'docs',
12
+ urlPrefix: '/docs',
13
+ cleanUrls: true,
14
+ srcExclude: ['archive/**'],
15
+ },
16
+ },
17
+ },
18
+ gates: { preset: 'standard' },
19
+ })
@@ -0,0 +1,8 @@
1
+ .env*
2
+ *.log
3
+ *.map
4
+ *.mcpb
5
+ coverage/
6
+ test/
7
+ tests/
8
+ src/
package/mcpb/icon.png ADDED
Binary file
@@ -0,0 +1,96 @@
1
+ {
2
+ "manifest_version": "0.3",
3
+ "name": "doc-bridge",
4
+ "display_name": "Doc Bridge",
5
+ "version": "1.3.0",
6
+ "description": "Deterministic repository handoffs for coding agents, running locally without an LLM or API key.",
7
+ "long_description": "Doc Bridge turns a repository's own documentation and ownership metadata into deterministic handoffs: where an agent should start, which paths it may edit, which checks it must run, and when a human must take over. The local connector exposes the same read-only contract available through Doc Bridge CLI and CI.",
8
+ "author": {
9
+ "name": "AgentsKit",
10
+ "url": "https://agentskit.io"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/AgentsKit-io/doc-bridge.git"
15
+ },
16
+ "homepage": "https://doc-bridge.agentskit.io/",
17
+ "documentation": "https://doc-bridge.agentskit.io/docs/mcp",
18
+ "support": "https://github.com/AgentsKit-io/doc-bridge/issues",
19
+ "icon": "icon.png",
20
+ "server": {
21
+ "type": "node",
22
+ "entry_point": "server/ak-docs.js",
23
+ "mcp_config": {
24
+ "command": "node",
25
+ "args": [
26
+ "${__dirname}/server/ak-docs.js",
27
+ "mcp",
28
+ "--config",
29
+ "${user_config.project_config}"
30
+ ],
31
+ "env": {}
32
+ }
33
+ },
34
+ "tools": [
35
+ {
36
+ "name": "handoff.resolve",
37
+ "description": "Resolve a package or ownership id to its deterministic AgentHandoff."
38
+ },
39
+ {
40
+ "name": "doc.search",
41
+ "description": "Search the deterministic Doc Bridge index for repository documentation."
42
+ },
43
+ {
44
+ "name": "doc.get",
45
+ "description": "Read one indexed agent documentation file by id or indexed path."
46
+ },
47
+ {
48
+ "name": "gate.status",
49
+ "description": "Evaluate documentation gates without writing files."
50
+ },
51
+ {
52
+ "name": "retriever.query",
53
+ "description": "Return relevant local Doc Bridge index chunks for a query."
54
+ },
55
+ {
56
+ "name": "memory.classify",
57
+ "description": "Classify local memory candidates into agent, human, playbook, or discard routes."
58
+ },
59
+ {
60
+ "name": "memory.promoteDraft",
61
+ "description": "Build a reviewable draft promotion body from local memory candidates without publishing it."
62
+ },
63
+ {
64
+ "name": "registry.topology",
65
+ "description": "Return the static Doc Bridge curator and delegate topology."
66
+ }
67
+ ],
68
+ "tools_generated": false,
69
+ "keywords": [
70
+ "coding-agents",
71
+ "documentation",
72
+ "handoff",
73
+ "repository",
74
+ "developer-tools"
75
+ ],
76
+ "license": "MIT",
77
+ "privacy_policies": [
78
+ "https://github.com/AgentsKit-io/doc-bridge/blob/master/PRIVACY.md"
79
+ ],
80
+ "compatibility": {
81
+ "platforms": [
82
+ "darwin"
83
+ ],
84
+ "runtimes": {
85
+ "node": ">=22"
86
+ }
87
+ },
88
+ "user_config": {
89
+ "project_config": {
90
+ "type": "file",
91
+ "title": "Doc Bridge configuration",
92
+ "description": "Select the doc-bridge.config.json file at the root of the repository Doc Bridge may read.",
93
+ "required": true
94
+ }
95
+ }
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentskit/doc-bridge",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "mcpName": "io.github.AgentsKit-io/doc-bridge",
5
5
  "description": "Human↔agent documentation bridge — deterministic handoffs, doc-site links, memory→docs, optional AgentsKit RAG/chat.",
6
6
  "type": "module",
@@ -31,11 +31,13 @@
31
31
  "docs",
32
32
  "examples",
33
33
  "README.md",
34
+ "PRIVACY.md",
34
35
  "CONTRIBUTING.md",
35
36
  "SECURITY.md",
36
37
  "CODE_OF_CONDUCT.md",
37
38
  "CHANGELOG.md",
38
39
  "LICENSE",
40
+ "mcpb",
39
41
  "tsup.config.ts",
40
42
  "tsconfig.json",
41
43
  "src"
@@ -53,6 +55,11 @@
53
55
  "smoke:docsites": "node scripts/smoke-docsites.mjs",
54
56
  "smoke:real-docsites": "node scripts/smoke-real-docsites.mjs",
55
57
  "smoke:ollama": "node scripts/smoke-ollama.mjs",
58
+ "mcpb:stage": "npm run build && node scripts/build-mcpb.mjs stage",
59
+ "mcpb:validate": "node scripts/build-mcpb.mjs validate",
60
+ "mcpb:smoke": "node scripts/smoke-mcpb.mjs",
61
+ "mcpb:pack": "npm run mcpb:stage && npm run mcpb:smoke && node scripts/build-mcpb.mjs pack",
62
+ "test:mcpb": "node --test scripts/mcpb-contract.test.mjs",
56
63
  "coverage:badge": "node scripts/update-coverage-badge.mjs",
57
64
  "changeset": "changeset",
58
65
  "version-packages": "changeset version && node scripts/sync-version.mjs",
@@ -96,7 +103,9 @@
96
103
  "access": "public"
97
104
  },
98
105
  "dependencies": {
99
- "mermaid": "^11.16.0",
106
+ "github-slugger": "^2.0.0",
107
+ "mermaid": "^11.16.1",
108
+ "minimatch": "^10.2.6",
100
109
  "zod": "^3.24.2"
101
110
  },
102
111
  "peerDependencies": {
@@ -132,6 +141,7 @@
132
141
  "@agentskit/core": "1.12.3",
133
142
  "@agentskit/ink": "0.10.4",
134
143
  "@agentskit/react": "0.7.4",
144
+ "@anthropic-ai/mcpb": "2.1.2",
135
145
  "@changesets/cli": "^2.31.0",
136
146
  "@lhci/cli": "^0.15.1",
137
147
  "@playwright/test": "1.61.1",
@@ -140,11 +150,12 @@
140
150
  "@types/react": "19.2.17",
141
151
  "@types/react-dom": "19.2.3",
142
152
  "@vitest/coverage-v8": "4.1.10",
153
+ "esbuild": "^0.28.1",
143
154
  "fumadocs-core": "15.6.5",
144
155
  "fumadocs-mdx": "11.6.4",
145
156
  "fumadocs-ui": "15.6.5",
146
157
  "lucide-react": "0.460.0",
147
- "next": "15.5.19",
158
+ "next": "15.5.21",
148
159
  "react": "19.2.0",
149
160
  "react-dom": "19.2.0",
150
161
  "tailwindcss": "4.3.2",
@@ -152,14 +163,6 @@
152
163
  "typescript": "^6.0.3",
153
164
  "vitest": "^4.1.9"
154
165
  },
155
- "pnpm": {
156
- "overrides": {
157
- "esbuild": "^0.28.1",
158
- "postcss": "8.5.19",
159
- "tmp": "0.2.7",
160
- "uuid": "11.1.1"
161
- }
162
- },
163
166
  "directories": {
164
167
  "doc": "docs",
165
168
  "example": "examples",
@@ -8,6 +8,8 @@ export const HumanCorpusPluginIdSchema = z.enum([
8
8
  'docusaurus',
9
9
  'mkdocs',
10
10
  'vitepress',
11
+ 'starlight',
12
+ 'nextra',
11
13
  'custom',
12
14
  ])
13
15
 
@@ -71,7 +73,7 @@ export const OwnershipEntrySchema = z
71
73
  export const RoutingConfigSchema = z
72
74
  .object({
73
75
  plugin: z
74
- .enum(['pnpm-monorepo', 'npm-workspaces', 'yarn-workspaces', 'pattern-files', 'custom'])
76
+ .enum(['pnpm-monorepo', 'npm-workspaces', 'yarn-workspaces', 'nx', 'pattern-files', 'custom'])
75
77
  .optional(),
76
78
  options: z
77
79
  .object({
@@ -70,6 +70,7 @@ export const collectPackages = (
70
70
  id: pkg.id,
71
71
  path: existing.path || pkg.path,
72
72
  ...(pkg.name ? { name: pkg.name } : existing.name ? { name: existing.name } : {}),
73
+ ...(pkg.checks ? { checks: pkg.checks } : existing.checks ? { checks: existing.checks } : {}),
73
74
  })
74
75
  }
75
76
 
@@ -163,6 +164,7 @@ export const buildLookup = (
163
164
  const checks = [
164
165
  ...(override?.checks ??
165
166
  fm?.checks ??
167
+ pkg.checks ??
166
168
  defaultChecksForTarget(root, {
167
169
  packageId: pkg.id,
168
170
  packagePath: path,
@@ -9,6 +9,7 @@ import { renderCapabilitiesJson } from './capabilities.js'
9
9
  import { sha256NormalizedV1 } from './content-hash.js'
10
10
  import { renderLlmsTxt } from './llms-txt.js'
11
11
  import { scanHumanDocs } from './human-adapters/index.js'
12
+ import { discoverNxProjects } from './plugins/nx.js'
12
13
  import { discoverPnpmPackages } from './plugins/pnpm-monorepo.js'
13
14
  import { scanAgentCorpus } from './scan-corpus.js'
14
15
 
@@ -66,7 +67,12 @@ export const buildDocBridgeIndex = (opts: BuildIndexOptions): BuildIndexResult =
66
67
  config.routing?.plugin === 'npm-workspaces' ||
67
68
  config.routing?.plugin === 'yarn-workspaces'
68
69
 
69
- const discovered = shouldDiscover ? discoverPnpmPackages(root, config) : []
70
+ const discovered =
71
+ config.routing?.plugin === 'nx'
72
+ ? discoverNxProjects(root, config)
73
+ : shouldDiscover
74
+ ? discoverPnpmPackages(root, config)
75
+ : []
70
76
  const packages = collectPackages(config, discovered, corpus)
71
77
  const humanDocs = scanHumanDocs(root, config)
72
78
 
@@ -4,8 +4,11 @@ import { resolve, sep } from 'node:path'
4
4
  import type { DocBridgeConfigV1, HumanCorpusConfig } from '../../config/schema.js'
5
5
  import { docusaurusAdapter } from './docusaurus.js'
6
6
  import { fumadocsAdapter } from './fumadocs.js'
7
+ import { nextraAdapter } from './nextra.js'
7
8
  import type { HumanAdapter, HumanDocMap, HumanDocRecord } from './core.js'
8
9
  import { plainMarkdownAdapter } from './plain-markdown.js'
10
+ import { starlightAdapter } from './starlight.js'
11
+ import { vitepressAdapter } from './vitepress.js'
9
12
 
10
13
  export type { HumanAdapter, HumanDocMap, HumanDocRecord } from './core.js'
11
14
 
@@ -13,6 +16,9 @@ const ADAPTERS: readonly HumanAdapter[] = [
13
16
  plainMarkdownAdapter,
14
17
  fumadocsAdapter,
15
18
  docusaurusAdapter,
19
+ vitepressAdapter,
20
+ starlightAdapter,
21
+ nextraAdapter,
16
22
  ]
17
23
 
18
24
  const humanConfigs = (config: DocBridgeConfigV1): HumanCorpusConfig[] => {
@@ -0,0 +1,43 @@
1
+ import {
2
+ optionString,
3
+ parseFrontmatter,
4
+ routeSlug,
5
+ scanMarkdownDocs,
6
+ type HumanAdapter,
7
+ type HumanDocRecord,
8
+ } from './core.js'
9
+
10
+ const nextraRecordId = (relPath: string, raw: string): string => {
11
+ const frontmatter = parseFrontmatter(raw)
12
+ return frontmatter.package ?? frontmatter.module ?? frontmatter.id ?? (routeSlug(relPath) || 'index')
13
+ }
14
+
15
+ const isIndexPage = (path: string): boolean => /(^|[/\\])index\.mdx?$/i.test(path)
16
+
17
+ const resolveRouteCollisions = (records: readonly HumanDocRecord[]): HumanDocRecord[] => {
18
+ const byUrl = new Map<string, { readonly record: HumanDocRecord; readonly indexPage: boolean }>()
19
+ for (const record of records) {
20
+ const normalized = { ...record, url: record.url || '/' }
21
+ const indexPage = isIndexPage(record.path)
22
+ const existing = byUrl.get(normalized.url)
23
+ if (!existing || indexPage || !existing.indexPage) {
24
+ byUrl.set(normalized.url, { record: normalized, indexPage })
25
+ }
26
+ }
27
+ return [...byUrl.values()].map(({ record }) => record)
28
+ }
29
+
30
+ export const nextraAdapter: HumanAdapter = {
31
+ plugin: 'nextra',
32
+ scan: ({ root, config }) => {
33
+ const contentDir = optionString(config.options, ['contentDir', 'docsDir', 'root'])
34
+ if (!contentDir) return []
35
+
36
+ return resolveRouteCollisions(
37
+ scanMarkdownDocs(root, contentDir, {
38
+ idForDoc: nextraRecordId,
39
+ urlPrefix: optionString(config.options, ['urlPrefix', 'contentDirBasePath']) ?? '/',
40
+ }),
41
+ )
42
+ },
43
+ }
@@ -0,0 +1,40 @@
1
+ import { slug as githubSlug } from 'github-slugger'
2
+
3
+ import {
4
+ optionString,
5
+ parseFrontmatter,
6
+ routeSlug,
7
+ scanMarkdownDocs,
8
+ type HumanAdapter,
9
+ } from './core.js'
10
+
11
+ const isStarlightPage = (relPath: string, raw: string): boolean => {
12
+ if (relPath.split('/').some((part) => part.startsWith('.') || part.startsWith('_'))) return false
13
+ return parseFrontmatter(raw).draft !== 'true'
14
+ }
15
+
16
+ const starlightFileSlug = (relPath: string): string =>
17
+ routeSlug(relPath)
18
+ .split('/')
19
+ .map((part) => githubSlug(part))
20
+ .filter(Boolean)
21
+ .join('/')
22
+
23
+ const starlightSlug = (relPath: string, raw: string): string => {
24
+ const slug = parseFrontmatter(raw).slug
25
+ return slug ? slug.replace(/^\/+|\/+$/g, '') : starlightFileSlug(relPath)
26
+ }
27
+
28
+ export const starlightAdapter: HumanAdapter = {
29
+ plugin: 'starlight',
30
+ scan: ({ root, config }) => {
31
+ const contentDir = optionString(config.options, ['contentDir', 'docsDir', 'root'])
32
+ if (!contentDir) return []
33
+
34
+ return scanMarkdownDocs(root, contentDir, {
35
+ includeRelPath: isStarlightPage,
36
+ slugForDoc: starlightSlug,
37
+ urlPrefix: config.options?.urlPrefix,
38
+ })
39
+ },
40
+ }
@@ -0,0 +1,43 @@
1
+ import { minimatch } from 'minimatch'
2
+
3
+ import { optionString, routeSlug, scanMarkdownDocs, type HumanAdapter } from './core.js'
4
+
5
+ const isVitePressPage = (relPath: string): boolean =>
6
+ !relPath.split('/').some((part) => part === '.vitepress' || part.startsWith('.'))
7
+
8
+ const srcExcludePatterns = (value: unknown): readonly string[] => {
9
+ if (value === undefined) return []
10
+ if (!Array.isArray(value) || value.length > 64) {
11
+ throw new Error('VitePress srcExclude must be an array of at most 64 glob patterns.')
12
+ }
13
+ return value.map((pattern) => {
14
+ if (typeof pattern !== 'string' || !pattern || pattern.length > 256 || pattern.includes('\0')) {
15
+ throw new Error('Each VitePress srcExclude pattern must be a non-empty string up to 256 characters.')
16
+ }
17
+ return pattern
18
+ })
19
+ }
20
+
21
+ const isExcluded = (relPath: string, patterns: readonly string[]): boolean =>
22
+ patterns.some((pattern) => minimatch(relPath, pattern, { dot: true }))
23
+
24
+ const vitepressSlug = (relPath: string, cleanUrls: boolean): string => {
25
+ const slug = routeSlug(relPath)
26
+ if (cleanUrls || /(?:^|\/)index\.mdx?$/.test(relPath)) return slug
27
+ return `${slug}.html`
28
+ }
29
+
30
+ export const vitepressAdapter: HumanAdapter = {
31
+ plugin: 'vitepress',
32
+ scan: ({ root, config }) => {
33
+ const docsDir = optionString(config.options, ['docsDir', 'root', 'srcDir'])
34
+ if (!docsDir) return []
35
+ const srcExclude = srcExcludePatterns(config.options?.srcExclude)
36
+
37
+ return scanMarkdownDocs(root, docsDir, {
38
+ includeRelPath: (relPath) => isVitePressPage(relPath) && !isExcluded(relPath, srcExclude),
39
+ slugForDoc: (relPath) => vitepressSlug(relPath, config.options?.cleanUrls === true),
40
+ urlPrefix: config.options?.urlPrefix,
41
+ })
42
+ },
43
+ }