skrypt-ai 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.
- package/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/autofix/index.d.ts +46 -0
- package/dist/autofix/index.js +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/commands/autofix.d.ts +2 -0
- package/dist/commands/autofix.js +143 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +320 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/review-pr.d.ts +2 -0
- package/dist/commands/review-pr.js +117 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +142 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +24 -0
- package/dist/config/types.js +34 -0
- package/dist/generator/generator.d.ts +15 -0
- package/dist/generator/generator.js +144 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +4 -0
- package/dist/generator/organizer.d.ts +29 -0
- package/dist/generator/organizer.js +222 -0
- package/dist/generator/types.d.ts +83 -0
- package/dist/generator/types.js +1 -0
- package/dist/generator/writer.d.ts +28 -0
- package/dist/generator/writer.js +320 -0
- package/dist/github/pr-comments.d.ts +40 -0
- package/dist/github/pr-comments.js +308 -0
- package/dist/llm/anthropic-client.d.ts +16 -0
- package/dist/llm/anthropic-client.js +92 -0
- package/dist/llm/index.d.ts +53 -0
- package/dist/llm/index.js +400 -0
- package/dist/llm/llm.manual-test.d.ts +1 -0
- package/dist/llm/llm.manual-test.js +112 -0
- package/dist/llm/llm.mock-test.d.ts +4 -0
- package/dist/llm/llm.mock-test.js +79 -0
- package/dist/llm/openai-client.d.ts +17 -0
- package/dist/llm/openai-client.js +90 -0
- package/dist/llm/types.d.ts +60 -0
- package/dist/llm/types.js +20 -0
- package/dist/scanner/content-type.d.ts +39 -0
- package/dist/scanner/content-type.js +194 -0
- package/dist/scanner/content-type.test.d.ts +1 -0
- package/dist/scanner/content-type.test.js +231 -0
- package/dist/scanner/go.d.ts +20 -0
- package/dist/scanner/go.js +269 -0
- package/dist/scanner/index.d.ts +21 -0
- package/dist/scanner/index.js +137 -0
- package/dist/scanner/python.d.ts +6 -0
- package/dist/scanner/python.js +57 -0
- package/dist/scanner/python_parser.py +230 -0
- package/dist/scanner/rust.d.ts +23 -0
- package/dist/scanner/rust.js +304 -0
- package/dist/scanner/scanner.test.d.ts +1 -0
- package/dist/scanner/scanner.test.js +210 -0
- package/dist/scanner/types.d.ts +50 -0
- package/dist/scanner/types.js +1 -0
- package/dist/scanner/typescript.d.ts +34 -0
- package/dist/scanner/typescript.js +327 -0
- package/dist/scanner/typescript.manual-test.d.ts +1 -0
- package/dist/scanner/typescript.manual-test.js +112 -0
- package/dist/template/docs.json +32 -0
- package/dist/template/mdx-components.tsx +62 -0
- package/dist/template/next-env.d.ts +6 -0
- package/dist/template/next.config.mjs +17 -0
- package/dist/template/package.json +39 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/search-index.json +1 -0
- package/dist/template/scripts/build-search-index.mjs +120 -0
- package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
- package/dist/template/src/app/api/openapi/route.ts +48 -0
- package/dist/template/src/app/api/rate-limit/route.ts +84 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
- package/dist/template/src/app/docs/layout.tsx +9 -0
- package/dist/template/src/app/docs/page.mdx +67 -0
- package/dist/template/src/app/error.tsx +63 -0
- package/dist/template/src/app/layout.tsx +71 -0
- package/dist/template/src/app/page.tsx +18 -0
- package/dist/template/src/app/reference/route.ts +36 -0
- package/dist/template/src/app/robots.ts +14 -0
- package/dist/template/src/app/sitemap.ts +64 -0
- package/dist/template/src/components/breadcrumbs.tsx +41 -0
- package/dist/template/src/components/copy-button.tsx +29 -0
- package/dist/template/src/components/docs-layout.tsx +35 -0
- package/dist/template/src/components/edit-link.tsx +39 -0
- package/dist/template/src/components/feedback.tsx +52 -0
- package/dist/template/src/components/header.tsx +66 -0
- package/dist/template/src/components/mdx/accordion.tsx +48 -0
- package/dist/template/src/components/mdx/api-badge.tsx +57 -0
- package/dist/template/src/components/mdx/callout.tsx +111 -0
- package/dist/template/src/components/mdx/card.tsx +62 -0
- package/dist/template/src/components/mdx/changelog.tsx +57 -0
- package/dist/template/src/components/mdx/code-block.tsx +42 -0
- package/dist/template/src/components/mdx/code-group.tsx +125 -0
- package/dist/template/src/components/mdx/code-playground.tsx +322 -0
- package/dist/template/src/components/mdx/go-playground.tsx +235 -0
- package/dist/template/src/components/mdx/heading.tsx +37 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
- package/dist/template/src/components/mdx/index.tsx +15 -0
- package/dist/template/src/components/mdx/param-table.tsx +71 -0
- package/dist/template/src/components/mdx/python-playground.tsx +293 -0
- package/dist/template/src/components/mdx/steps.tsx +43 -0
- package/dist/template/src/components/mdx/tabs.tsx +81 -0
- package/dist/template/src/components/rate-limit-display.tsx +183 -0
- package/dist/template/src/components/search-dialog.tsx +178 -0
- package/dist/template/src/components/sidebar.tsx +129 -0
- package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
- package/dist/template/src/components/table-of-contents.tsx +84 -0
- package/dist/template/src/components/theme-toggle.tsx +46 -0
- package/dist/template/src/components/version-selector.tsx +61 -0
- package/dist/template/src/contexts/syntax-theme.tsx +52 -0
- package/dist/template/src/lib/highlight.ts +83 -0
- package/dist/template/src/lib/search-types.ts +37 -0
- package/dist/template/src/lib/search.ts +125 -0
- package/dist/template/src/lib/utils.ts +6 -0
- package/dist/template/src/styles/globals.css +152 -0
- package/dist/template/tsconfig.json +25 -0
- package/dist/template/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { DEFAULT_CONFIG, PROVIDER_ENV_KEYS } from './types.js';
|
|
5
|
+
const VALID_PROVIDERS = ['deepseek', 'openai', 'anthropic', 'google', 'ollama', 'openrouter'];
|
|
6
|
+
const CONFIG_FILENAMES = ['.skrypt.yaml', '.skrypt.yml', 'skrypt.yaml', 'skrypt.yml'];
|
|
7
|
+
export function findConfigFile(dir) {
|
|
8
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
9
|
+
const filepath = join(dir, filename);
|
|
10
|
+
if (existsSync(filepath)) {
|
|
11
|
+
return filepath;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export function loadConfig(configPath) {
|
|
17
|
+
// If explicit path provided, use it
|
|
18
|
+
if (configPath) {
|
|
19
|
+
if (!existsSync(configPath)) {
|
|
20
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
21
|
+
}
|
|
22
|
+
return parseConfigFile(configPath);
|
|
23
|
+
}
|
|
24
|
+
// Otherwise look for config in current directory
|
|
25
|
+
const foundPath = findConfigFile(process.cwd());
|
|
26
|
+
if (foundPath) {
|
|
27
|
+
return parseConfigFile(foundPath);
|
|
28
|
+
}
|
|
29
|
+
// No config file, use defaults
|
|
30
|
+
return DEFAULT_CONFIG;
|
|
31
|
+
}
|
|
32
|
+
function parseConfigFile(filepath) {
|
|
33
|
+
const content = readFileSync(filepath, 'utf-8');
|
|
34
|
+
const parsed = yaml.load(content);
|
|
35
|
+
// Merge with defaults
|
|
36
|
+
return mergeConfig(DEFAULT_CONFIG, parsed);
|
|
37
|
+
}
|
|
38
|
+
function mergeConfig(defaults, overrides) {
|
|
39
|
+
return {
|
|
40
|
+
version: overrides.version ?? defaults.version,
|
|
41
|
+
source: {
|
|
42
|
+
...defaults.source,
|
|
43
|
+
...overrides.source
|
|
44
|
+
},
|
|
45
|
+
output: {
|
|
46
|
+
...defaults.output,
|
|
47
|
+
...overrides.output
|
|
48
|
+
},
|
|
49
|
+
llm: {
|
|
50
|
+
...defaults.llm,
|
|
51
|
+
...overrides.llm
|
|
52
|
+
},
|
|
53
|
+
license: overrides.license ?? defaults.license
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function validateConfig(config) {
|
|
57
|
+
const errors = [];
|
|
58
|
+
if (config.version !== 1) {
|
|
59
|
+
errors.push(`Unsupported config version: ${config.version}`);
|
|
60
|
+
}
|
|
61
|
+
if (!config.source.path) {
|
|
62
|
+
errors.push('source.path is required');
|
|
63
|
+
}
|
|
64
|
+
if (!config.output.path) {
|
|
65
|
+
errors.push('output.path is required');
|
|
66
|
+
}
|
|
67
|
+
if (!VALID_PROVIDERS.includes(config.llm.provider)) {
|
|
68
|
+
errors.push(`Invalid LLM provider: ${config.llm.provider}. Valid: ${VALID_PROVIDERS.join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
export function getRequiredEnvKey(provider) {
|
|
73
|
+
return PROVIDER_ENV_KEYS[provider] || null;
|
|
74
|
+
}
|
|
75
|
+
export function checkApiKey(provider) {
|
|
76
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
77
|
+
// Ollama doesn't need an API key
|
|
78
|
+
if (!envKey) {
|
|
79
|
+
return { ok: true, envKey: null };
|
|
80
|
+
}
|
|
81
|
+
return { ok: !!process.env[envKey], envKey };
|
|
82
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface SourceConfig {
|
|
2
|
+
path: string;
|
|
3
|
+
include?: string[];
|
|
4
|
+
exclude?: string[];
|
|
5
|
+
}
|
|
6
|
+
export type LLMProvider = 'deepseek' | 'openai' | 'anthropic' | 'google' | 'ollama' | 'openrouter';
|
|
7
|
+
export interface LLMConfig {
|
|
8
|
+
provider: LLMProvider;
|
|
9
|
+
model: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const PROVIDER_ENV_KEYS: Record<LLMProvider, string>;
|
|
13
|
+
export declare const DEFAULT_MODELS: Record<LLMProvider, string>;
|
|
14
|
+
export interface Config {
|
|
15
|
+
version: number;
|
|
16
|
+
source: SourceConfig;
|
|
17
|
+
output: {
|
|
18
|
+
path: string;
|
|
19
|
+
format: 'markdown' | 'mdx';
|
|
20
|
+
};
|
|
21
|
+
llm: LLMConfig;
|
|
22
|
+
license?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Provider -> env var mapping
|
|
2
|
+
export const PROVIDER_ENV_KEYS = {
|
|
3
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
4
|
+
openai: 'OPENAI_API_KEY',
|
|
5
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
6
|
+
google: 'GOOGLE_API_KEY',
|
|
7
|
+
ollama: '', // No key needed for local
|
|
8
|
+
openrouter: 'OPENROUTER_API_KEY'
|
|
9
|
+
};
|
|
10
|
+
// Default models per provider
|
|
11
|
+
export const DEFAULT_MODELS = {
|
|
12
|
+
deepseek: 'deepseek-v3',
|
|
13
|
+
openai: 'gpt-4.1',
|
|
14
|
+
anthropic: 'claude-sonnet-4-6',
|
|
15
|
+
google: 'gemini-2.5-flash',
|
|
16
|
+
ollama: 'qwen2.5-coder:32b',
|
|
17
|
+
openrouter: 'deepseek/deepseek-v3'
|
|
18
|
+
};
|
|
19
|
+
export const DEFAULT_CONFIG = {
|
|
20
|
+
version: 1,
|
|
21
|
+
source: {
|
|
22
|
+
path: './src',
|
|
23
|
+
include: ['**/*.py', '**/*.js', '**/*.ts', '**/*.go', '**/*.rs'],
|
|
24
|
+
exclude: ['**/node_modules/**', '**/__pycache__/**', '**/dist/**', '**/target/**']
|
|
25
|
+
},
|
|
26
|
+
output: {
|
|
27
|
+
path: './docs',
|
|
28
|
+
format: 'markdown'
|
|
29
|
+
},
|
|
30
|
+
llm: {
|
|
31
|
+
provider: 'deepseek',
|
|
32
|
+
model: 'deepseek-v3'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
import { LLMClient } from '../llm/index.js';
|
|
3
|
+
import { GeneratedDoc, GenerationOptions, GenerationProgress } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generate documentation for an API element (no testing)
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateForElement(element: APIElement, client: LLMClient, options: GenerationOptions, onProgress?: (progress: GenerationProgress) => void): Promise<GeneratedDoc>;
|
|
8
|
+
/**
|
|
9
|
+
* Generate documentation for multiple elements
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateForElements(elements: APIElement[], client: LLMClient, options: GenerationOptions): Promise<GeneratedDoc[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Format generated docs as markdown file content
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatAsMarkdown(docs: GeneratedDoc[], title: string): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { generateDocumentation } from '../llm/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build enhanced context for LLM from API element
|
|
4
|
+
*/
|
|
5
|
+
function buildElementContext(element) {
|
|
6
|
+
return {
|
|
7
|
+
kind: element.kind,
|
|
8
|
+
name: element.name,
|
|
9
|
+
signature: element.signature,
|
|
10
|
+
parameters: element.parameters,
|
|
11
|
+
returnType: element.returnType,
|
|
12
|
+
docstring: element.docstring,
|
|
13
|
+
parentClass: element.parentClass,
|
|
14
|
+
imports: element.imports,
|
|
15
|
+
packageName: element.packageName,
|
|
16
|
+
sourceContext: element.sourceContext,
|
|
17
|
+
filePath: element.filePath
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Detect code language from file path
|
|
22
|
+
*/
|
|
23
|
+
function detectLanguageFromFile(filePath) {
|
|
24
|
+
if (filePath.endsWith('.py'))
|
|
25
|
+
return 'python';
|
|
26
|
+
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx'))
|
|
27
|
+
return 'typescript';
|
|
28
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx'))
|
|
29
|
+
return 'javascript';
|
|
30
|
+
if (filePath.endsWith('.go'))
|
|
31
|
+
return 'go';
|
|
32
|
+
if (filePath.endsWith('.rs'))
|
|
33
|
+
return 'rust';
|
|
34
|
+
return 'typescript';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate documentation for an API element (no testing)
|
|
38
|
+
*/
|
|
39
|
+
export async function generateForElement(element, client, options, onProgress) {
|
|
40
|
+
const report = (status) => {
|
|
41
|
+
onProgress?.({
|
|
42
|
+
current: 0,
|
|
43
|
+
total: 0,
|
|
44
|
+
element: element.name,
|
|
45
|
+
status
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const codeLanguage = detectLanguageFromFile(element.filePath);
|
|
49
|
+
const useMultiLang = options.multiLanguage ?? false;
|
|
50
|
+
report('generating');
|
|
51
|
+
try {
|
|
52
|
+
const elementContext = buildElementContext(element);
|
|
53
|
+
const result = await generateDocumentation(client, elementContext, { multiLanguage: useMultiLang });
|
|
54
|
+
report('done');
|
|
55
|
+
return {
|
|
56
|
+
element,
|
|
57
|
+
markdown: result.markdown,
|
|
58
|
+
codeExample: result.codeExample,
|
|
59
|
+
codeLanguage,
|
|
60
|
+
typescriptExample: result.typescriptExample,
|
|
61
|
+
pythonExample: result.pythonExample
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
report('failed');
|
|
66
|
+
return {
|
|
67
|
+
element,
|
|
68
|
+
markdown: '',
|
|
69
|
+
codeExample: '',
|
|
70
|
+
codeLanguage,
|
|
71
|
+
error: `Failed to generate: ${err}`
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate documentation for multiple elements
|
|
77
|
+
*/
|
|
78
|
+
export async function generateForElements(elements, client, options) {
|
|
79
|
+
const results = [];
|
|
80
|
+
for (let i = 0; i < elements.length; i++) {
|
|
81
|
+
const element = elements[i];
|
|
82
|
+
if (!element)
|
|
83
|
+
continue;
|
|
84
|
+
const doc = await generateForElement(element, client, options, (progress) => {
|
|
85
|
+
options.onProgress?.({
|
|
86
|
+
...progress,
|
|
87
|
+
current: i + 1,
|
|
88
|
+
total: elements.length
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
results.push(doc);
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format generated docs as markdown file content
|
|
97
|
+
*/
|
|
98
|
+
export function formatAsMarkdown(docs, title) {
|
|
99
|
+
let content = `# ${title}\n\n`;
|
|
100
|
+
const functions = docs.filter(d => d.element.kind === 'function');
|
|
101
|
+
const classes = docs.filter(d => d.element.kind === 'class');
|
|
102
|
+
const methods = docs.filter(d => d.element.kind === 'method');
|
|
103
|
+
if (functions.length > 0) {
|
|
104
|
+
content += '## Functions\n\n';
|
|
105
|
+
for (const doc of functions) {
|
|
106
|
+
content += formatDocSection(doc);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (classes.length > 0) {
|
|
110
|
+
content += '## Classes\n\n';
|
|
111
|
+
for (const doc of classes) {
|
|
112
|
+
content += formatDocSection(doc);
|
|
113
|
+
const classMethods = methods.filter(m => m.element.parentClass === doc.element.name);
|
|
114
|
+
if (classMethods.length > 0) {
|
|
115
|
+
content += '### Methods\n\n';
|
|
116
|
+
for (const method of classMethods) {
|
|
117
|
+
content += formatDocSection(method, '####');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return content;
|
|
123
|
+
}
|
|
124
|
+
function formatDocSection(doc, headingLevel = '###') {
|
|
125
|
+
let section = `${headingLevel} \`${doc.element.name}\`\n\n`;
|
|
126
|
+
section += `\`\`\`${doc.codeLanguage}\n${doc.element.signature}\n\`\`\`\n\n`;
|
|
127
|
+
if (doc.markdown) {
|
|
128
|
+
section += doc.markdown + '\n\n';
|
|
129
|
+
}
|
|
130
|
+
const hasMultiLang = doc.typescriptExample && doc.pythonExample;
|
|
131
|
+
if (hasMultiLang) {
|
|
132
|
+
section += '**Examples:**\n\n';
|
|
133
|
+
section += '<CodeGroup>\n\n';
|
|
134
|
+
section += `\`\`\`typescript example.ts\n${doc.typescriptExample}\n\`\`\`\n\n`;
|
|
135
|
+
section += `\`\`\`python example.py\n${doc.pythonExample}\n\`\`\`\n\n`;
|
|
136
|
+
section += '</CodeGroup>\n\n';
|
|
137
|
+
}
|
|
138
|
+
else if (doc.codeExample) {
|
|
139
|
+
section += '**Example:**\n\n';
|
|
140
|
+
const ext = doc.codeLanguage === 'typescript' ? 'ts' : doc.codeLanguage === 'python' ? 'py' : 'js';
|
|
141
|
+
section += `\`\`\`${doc.codeLanguage} example.${ext}\n${doc.codeExample}\n\`\`\`\n\n`;
|
|
142
|
+
}
|
|
143
|
+
return section;
|
|
144
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GeneratedDoc, Topic, TopicConfig, CrossReference, NavigationItem } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default topic configuration with common patterns
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_TOPIC_CONFIG: TopicConfig;
|
|
6
|
+
/**
|
|
7
|
+
* Organize generated docs into topics
|
|
8
|
+
*/
|
|
9
|
+
export declare function organizeByTopic(docs: GeneratedDoc[], config?: TopicConfig): Topic[];
|
|
10
|
+
/**
|
|
11
|
+
* Detect cross-references between elements
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectCrossReferences(docs: GeneratedDoc[]): CrossReference[];
|
|
14
|
+
/**
|
|
15
|
+
* Get cross-references for a specific element
|
|
16
|
+
*/
|
|
17
|
+
export declare function getCrossRefsForElement(elementName: string, allRefs: CrossReference[]): CrossReference[];
|
|
18
|
+
/**
|
|
19
|
+
* Build navigation structure from topics
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildNavigation(topics: Topic[]): NavigationItem[];
|
|
22
|
+
/**
|
|
23
|
+
* Generate a sidebar configuration (works with multiple doc platforms)
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateSidebarConfig(topics: Topic[]): object;
|
|
26
|
+
/**
|
|
27
|
+
* Merge user config with defaults
|
|
28
|
+
*/
|
|
29
|
+
export declare function mergeTopicConfig(userConfig: Partial<TopicConfig>, defaults?: TopicConfig): TopicConfig;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default topic configuration with common patterns
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_TOPIC_CONFIG = {
|
|
5
|
+
topics: {
|
|
6
|
+
'getting-started': { name: 'Getting Started', description: 'Quick start and setup guides', order: 0 },
|
|
7
|
+
'core': { name: 'Core API', description: 'Core functionality and main exports', order: 1 },
|
|
8
|
+
'middleware': { name: 'Middleware', description: 'Request/response middleware and handlers', order: 2 },
|
|
9
|
+
'memory': { name: 'Memory & Storage', description: 'Memory management and persistence', order: 3 },
|
|
10
|
+
'tools': { name: 'Tools & Utilities', description: 'Helper functions and utilities', order: 4 },
|
|
11
|
+
'types': { name: 'Types & Interfaces', description: 'Type definitions and interfaces', order: 5 },
|
|
12
|
+
'integrations': { name: 'Integrations', description: 'Third-party integrations', order: 6 },
|
|
13
|
+
'advanced': { name: 'Advanced', description: 'Advanced usage and configuration', order: 7 },
|
|
14
|
+
'other': { name: 'Other', description: 'Miscellaneous utilities', order: 99 }
|
|
15
|
+
},
|
|
16
|
+
rules: [
|
|
17
|
+
// File path patterns
|
|
18
|
+
{ topic: 'middleware', patterns: ['**/middleware*', '**/handler*'] },
|
|
19
|
+
{ topic: 'memory', patterns: ['**/memory*', '**/store*', '**/persist*', '**/cache*'] },
|
|
20
|
+
{ topic: 'tools', patterns: ['**/tool*', '**/util*', '**/helper*', '**/shared*'] },
|
|
21
|
+
{ topic: 'types', patterns: ['**/types*', '**/interface*', '**/schema*'] },
|
|
22
|
+
{ topic: 'core', patterns: ['**/index*', '**/main*', '**/core*', '**/client*'] },
|
|
23
|
+
{ topic: 'integrations', patterns: ['**/openai*', '**/anthropic*', '**/vercel*', '**/mastra*'] },
|
|
24
|
+
// Function name patterns
|
|
25
|
+
{ topic: 'middleware', namePatterns: ['^create.*Handler$', '^.*Middleware$', '^wrap.*$'] },
|
|
26
|
+
{ topic: 'memory', namePatterns: ['^save.*Memory', '^get.*Memory', '^.*Memory$', '^store.*$', '^persist.*$'] },
|
|
27
|
+
{ topic: 'tools', namePatterns: ['^create.*Tool$', '^.*Tool$', '^use.*$'] },
|
|
28
|
+
// Element kinds
|
|
29
|
+
{ topic: 'types', kinds: ['interface', 'type'] }
|
|
30
|
+
],
|
|
31
|
+
defaultTopic: 'other'
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Organize generated docs into topics
|
|
35
|
+
*/
|
|
36
|
+
export function organizeByTopic(docs, config = DEFAULT_TOPIC_CONFIG) {
|
|
37
|
+
const topicDocs = new Map();
|
|
38
|
+
// Initialize all topics
|
|
39
|
+
for (const topicId of Object.keys(config.topics)) {
|
|
40
|
+
topicDocs.set(topicId, []);
|
|
41
|
+
}
|
|
42
|
+
// Categorize each doc
|
|
43
|
+
for (const doc of docs) {
|
|
44
|
+
const topicId = inferTopic(doc, config);
|
|
45
|
+
if (!topicDocs.has(topicId)) {
|
|
46
|
+
topicDocs.set(topicId, []);
|
|
47
|
+
}
|
|
48
|
+
topicDocs.get(topicId).push(doc);
|
|
49
|
+
}
|
|
50
|
+
// Build topic objects
|
|
51
|
+
const topics = [];
|
|
52
|
+
for (const [topicId, topicDocList] of topicDocs.entries()) {
|
|
53
|
+
if (topicDocList.length === 0)
|
|
54
|
+
continue;
|
|
55
|
+
const topicConfig = config.topics[topicId] || { name: titleCase(topicId), order: 50 };
|
|
56
|
+
topics.push({
|
|
57
|
+
id: topicId,
|
|
58
|
+
name: topicConfig.name,
|
|
59
|
+
description: topicConfig.description,
|
|
60
|
+
order: topicConfig.order ?? 50,
|
|
61
|
+
docs: sortDocsWithinTopic(topicDocList)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Sort topics by order
|
|
65
|
+
return topics.sort((a, b) => a.order - b.order);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Infer the topic for a doc based on rules
|
|
69
|
+
*/
|
|
70
|
+
function inferTopic(doc, config) {
|
|
71
|
+
const { element } = doc;
|
|
72
|
+
for (const rule of config.rules) {
|
|
73
|
+
// Check file path patterns
|
|
74
|
+
if (rule.patterns) {
|
|
75
|
+
for (const pattern of rule.patterns) {
|
|
76
|
+
if (matchGlobPattern(element.filePath, pattern)) {
|
|
77
|
+
return rule.topic;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Check name patterns
|
|
82
|
+
if (rule.namePatterns) {
|
|
83
|
+
for (const pattern of rule.namePatterns) {
|
|
84
|
+
if (new RegExp(pattern, 'i').test(element.name)) {
|
|
85
|
+
return rule.topic;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Check element kinds
|
|
90
|
+
if (rule.kinds && rule.kinds.includes(element.kind)) {
|
|
91
|
+
return rule.topic;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return config.defaultTopic || 'other';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Simple glob pattern matching
|
|
98
|
+
*/
|
|
99
|
+
function matchGlobPattern(path, pattern) {
|
|
100
|
+
// Convert glob to regex
|
|
101
|
+
const regex = pattern
|
|
102
|
+
.replace(/\*\*/g, '___DOUBLESTAR___')
|
|
103
|
+
.replace(/\*/g, '[^/]*')
|
|
104
|
+
.replace(/___DOUBLESTAR___/g, '.*')
|
|
105
|
+
.replace(/\?/g, '.');
|
|
106
|
+
return new RegExp(regex, 'i').test(path);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Sort docs within a topic by importance
|
|
110
|
+
*/
|
|
111
|
+
function sortDocsWithinTopic(docs) {
|
|
112
|
+
return docs.sort((a, b) => {
|
|
113
|
+
// Classes first, then functions, then methods
|
|
114
|
+
const kindOrder = { class: 0, function: 1, method: 2 };
|
|
115
|
+
const aKind = kindOrder[a.element.kind] ?? 3;
|
|
116
|
+
const bKind = kindOrder[b.element.kind] ?? 3;
|
|
117
|
+
if (aKind !== bKind)
|
|
118
|
+
return aKind - bKind;
|
|
119
|
+
// Then by name
|
|
120
|
+
return a.element.name.localeCompare(b.element.name);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Detect cross-references between elements
|
|
125
|
+
*/
|
|
126
|
+
export function detectCrossReferences(docs) {
|
|
127
|
+
const refs = [];
|
|
128
|
+
const elementNames = new Set(docs.map(d => d.element.name));
|
|
129
|
+
for (const doc of docs) {
|
|
130
|
+
const { element } = doc;
|
|
131
|
+
// Check source context for references to other elements
|
|
132
|
+
const sourceContext = element.sourceContext || '';
|
|
133
|
+
const signature = element.signature || '';
|
|
134
|
+
for (const otherName of elementNames) {
|
|
135
|
+
if (otherName === element.name)
|
|
136
|
+
continue;
|
|
137
|
+
// Check if this element uses another element
|
|
138
|
+
if (sourceContext.includes(otherName) || signature.includes(otherName)) {
|
|
139
|
+
refs.push({
|
|
140
|
+
fromElement: element.name,
|
|
141
|
+
toElement: otherName,
|
|
142
|
+
relationship: 'uses'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Methods reference their parent class
|
|
147
|
+
if (element.parentClass && elementNames.has(element.parentClass)) {
|
|
148
|
+
refs.push({
|
|
149
|
+
fromElement: element.name,
|
|
150
|
+
toElement: element.parentClass,
|
|
151
|
+
relationship: 'related'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Add reverse references (used-by)
|
|
156
|
+
const usesRefs = refs.filter(r => r.relationship === 'uses');
|
|
157
|
+
for (const ref of usesRefs) {
|
|
158
|
+
refs.push({
|
|
159
|
+
fromElement: ref.toElement,
|
|
160
|
+
toElement: ref.fromElement,
|
|
161
|
+
relationship: 'used-by'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return refs;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get cross-references for a specific element
|
|
168
|
+
*/
|
|
169
|
+
export function getCrossRefsForElement(elementName, allRefs) {
|
|
170
|
+
return allRefs.filter(r => r.fromElement === elementName);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Build navigation structure from topics
|
|
174
|
+
*/
|
|
175
|
+
export function buildNavigation(topics) {
|
|
176
|
+
return topics.map(topic => ({
|
|
177
|
+
title: topic.name,
|
|
178
|
+
path: `/${topic.id}`,
|
|
179
|
+
children: topic.docs.map(doc => ({
|
|
180
|
+
title: doc.element.name,
|
|
181
|
+
path: `/${topic.id}#${slugify(doc.element.name)}`
|
|
182
|
+
}))
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Generate a sidebar configuration (works with multiple doc platforms)
|
|
187
|
+
*/
|
|
188
|
+
export function generateSidebarConfig(topics) {
|
|
189
|
+
return {
|
|
190
|
+
navigation: topics.map(topic => ({
|
|
191
|
+
group: topic.name,
|
|
192
|
+
pages: topic.docs.map(doc => `${topic.id}/${slugify(doc.element.name)}`)
|
|
193
|
+
}))
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Convert string to title case
|
|
198
|
+
*/
|
|
199
|
+
function titleCase(str) {
|
|
200
|
+
return str
|
|
201
|
+
.replace(/-/g, ' ')
|
|
202
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Convert string to URL-safe slug
|
|
206
|
+
*/
|
|
207
|
+
function slugify(str) {
|
|
208
|
+
return str
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
211
|
+
.replace(/^-|-$/g, '');
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Merge user config with defaults
|
|
215
|
+
*/
|
|
216
|
+
export function mergeTopicConfig(userConfig, defaults = DEFAULT_TOPIC_CONFIG) {
|
|
217
|
+
return {
|
|
218
|
+
topics: { ...defaults.topics, ...userConfig.topics },
|
|
219
|
+
rules: [...defaults.rules, ...(userConfig.rules || [])],
|
|
220
|
+
defaultTopic: userConfig.defaultTopic ?? defaults.defaultTopic
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generated documentation for an API element
|
|
4
|
+
*/
|
|
5
|
+
export interface GeneratedDoc {
|
|
6
|
+
element: APIElement;
|
|
7
|
+
markdown: string;
|
|
8
|
+
codeExample: string;
|
|
9
|
+
codeLanguage: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
typescriptExample?: string;
|
|
12
|
+
pythonExample?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Progress callback for generation
|
|
16
|
+
*/
|
|
17
|
+
export interface GenerationProgress {
|
|
18
|
+
current: number;
|
|
19
|
+
total: number;
|
|
20
|
+
element: string;
|
|
21
|
+
status: 'generating' | 'done' | 'failed';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Options for generation
|
|
25
|
+
*/
|
|
26
|
+
export interface GenerationOptions {
|
|
27
|
+
onProgress?: (progress: GenerationProgress) => void;
|
|
28
|
+
multiLanguage?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Result of generating docs for a file
|
|
32
|
+
*/
|
|
33
|
+
export interface FileGenerationResult {
|
|
34
|
+
filePath: string;
|
|
35
|
+
docs: GeneratedDoc[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A topic groups related API elements by concept
|
|
39
|
+
*/
|
|
40
|
+
export interface Topic {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
order: number;
|
|
45
|
+
docs: GeneratedDoc[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Topic mapping rule for auto-categorization
|
|
49
|
+
*/
|
|
50
|
+
export interface TopicRule {
|
|
51
|
+
topic: string;
|
|
52
|
+
patterns?: string[];
|
|
53
|
+
namePatterns?: string[];
|
|
54
|
+
kinds?: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Topic configuration
|
|
58
|
+
*/
|
|
59
|
+
export interface TopicConfig {
|
|
60
|
+
topics: Record<string, {
|
|
61
|
+
name: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
order?: number;
|
|
64
|
+
}>;
|
|
65
|
+
rules: TopicRule[];
|
|
66
|
+
defaultTopic?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Navigation structure for generated docs
|
|
70
|
+
*/
|
|
71
|
+
export interface NavigationItem {
|
|
72
|
+
title: string;
|
|
73
|
+
path: string;
|
|
74
|
+
children?: NavigationItem[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Cross-reference between elements
|
|
78
|
+
*/
|
|
79
|
+
export interface CrossReference {
|
|
80
|
+
fromElement: string;
|
|
81
|
+
toElement: string;
|
|
82
|
+
relationship: 'uses' | 'used-by' | 'see-also' | 'related';
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { GeneratedDoc, FileGenerationResult, Topic } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate llms.txt file (Answer Engine Optimization)
|
|
4
|
+
* Format follows https://llmstxt.org convention
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeLlmsTxt(docs: GeneratedDoc[], outputDir: string, options?: {
|
|
7
|
+
projectName?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Write generated docs to output directory
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeDocsToDirectory(results: FileGenerationResult[], outputDir: string, sourceDir: string): Promise<{
|
|
14
|
+
filesWritten: number;
|
|
15
|
+
totalDocs: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Group generated docs by file
|
|
19
|
+
*/
|
|
20
|
+
export declare function groupDocsByFile(docs: GeneratedDoc[]): FileGenerationResult[];
|
|
21
|
+
/**
|
|
22
|
+
* Write docs organized by topic
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeDocsByTopic(docs: GeneratedDoc[], outputDir: string): Promise<{
|
|
25
|
+
filesWritten: number;
|
|
26
|
+
totalDocs: number;
|
|
27
|
+
topics: Topic[];
|
|
28
|
+
}>;
|