legacyver 2.1.0 → 2.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/.agent/skills/openspec-apply-change/SKILL.md +156 -0
- package/.agent/skills/openspec-archive-change/SKILL.md +114 -0
- package/.agent/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.agent/skills/openspec-continue-change/SKILL.md +118 -0
- package/.agent/skills/openspec-explore/SKILL.md +290 -0
- package/.agent/skills/openspec-ff-change/SKILL.md +101 -0
- package/.agent/skills/openspec-new-change/SKILL.md +74 -0
- package/.agent/skills/openspec-onboard/SKILL.md +529 -0
- package/.agent/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.agent/skills/openspec-verify-change/SKILL.md +168 -0
- package/.agent/workflows/opsx-apply.md +149 -0
- package/.agent/workflows/opsx-archive.md +154 -0
- package/.agent/workflows/opsx-bulk-archive.md +239 -0
- package/.agent/workflows/opsx-continue.md +111 -0
- package/.agent/workflows/opsx-explore.md +171 -0
- package/.agent/workflows/opsx-ff.md +91 -0
- package/.agent/workflows/opsx-new.md +66 -0
- package/.agent/workflows/opsx-onboard.md +522 -0
- package/.agent/workflows/opsx-sync.md +131 -0
- package/.agent/workflows/opsx-verify.md +161 -0
- package/.github/prompts/opsx-apply.prompt.md +149 -0
- package/.github/prompts/opsx-archive.prompt.md +154 -0
- package/.github/prompts/opsx-bulk-archive.prompt.md +239 -0
- package/.github/prompts/opsx-continue.prompt.md +111 -0
- package/.github/prompts/opsx-explore.prompt.md +171 -0
- package/.github/prompts/opsx-ff.prompt.md +91 -0
- package/.github/prompts/opsx-new.prompt.md +66 -0
- package/.github/prompts/opsx-onboard.prompt.md +522 -0
- package/.github/prompts/opsx-sync.prompt.md +131 -0
- package/.github/prompts/opsx-verify.prompt.md +161 -0
- package/.github/skills/openspec-apply-change/SKILL.md +156 -0
- package/.github/skills/openspec-archive-change/SKILL.md +114 -0
- package/.github/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.github/skills/openspec-continue-change/SKILL.md +118 -0
- package/.github/skills/openspec-explore/SKILL.md +290 -0
- package/.github/skills/openspec-ff-change/SKILL.md +101 -0
- package/.github/skills/openspec-new-change/SKILL.md +74 -0
- package/.github/skills/openspec-onboard/SKILL.md +529 -0
- package/.github/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.github/skills/openspec-verify-change/SKILL.md +168 -0
- package/.legacyverignore.example +43 -0
- package/.legacyverrc +7 -0
- package/.opencode/command/opsx-apply.md +149 -0
- package/.opencode/command/opsx-archive.md +154 -0
- package/.opencode/command/opsx-bulk-archive.md +239 -0
- package/.opencode/command/opsx-continue.md +111 -0
- package/.opencode/command/opsx-explore.md +171 -0
- package/.opencode/command/opsx-ff.md +91 -0
- package/.opencode/command/opsx-new.md +66 -0
- package/.opencode/command/opsx-onboard.md +522 -0
- package/.opencode/command/opsx-sync.md +131 -0
- package/.opencode/command/opsx-verify.md +161 -0
- package/.opencode/skills/openspec-apply-change/SKILL.md +156 -0
- package/.opencode/skills/openspec-archive-change/SKILL.md +114 -0
- package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.opencode/skills/openspec-continue-change/SKILL.md +118 -0
- package/.opencode/skills/openspec-explore/SKILL.md +290 -0
- package/.opencode/skills/openspec-ff-change/SKILL.md +101 -0
- package/.opencode/skills/openspec-new-change/SKILL.md +74 -0
- package/.opencode/skills/openspec-onboard/SKILL.md +529 -0
- package/.opencode/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.opencode/skills/openspec-verify-change/SKILL.md +168 -0
- package/LICENSE +1 -1
- package/README.md +128 -83
- package/bin/legacyver.js +48 -25
- package/legacyver-docs/SUMMARY.md +3 -0
- package/legacyver-docs/components.md +57 -0
- package/legacyver-docs/index.md +15 -0
- package/nul +2 -0
- package/package.json +23 -25
- package/src/cache/hash.js +9 -10
- package/src/cache/index.js +43 -65
- package/src/cli/commands/analyze.js +212 -190
- package/src/cli/commands/cache.js +15 -35
- package/src/cli/commands/init.js +63 -107
- package/src/cli/commands/providers.js +56 -81
- package/src/cli/commands/version.js +7 -10
- package/src/cli/ui.js +58 -77
- package/src/crawler/filters.js +41 -40
- package/src/crawler/index.js +52 -36
- package/src/crawler/manifest.js +31 -43
- package/src/crawler/walk.js +32 -38
- package/src/llm/chunker.js +34 -56
- package/src/llm/cost-estimator.js +68 -51
- package/src/llm/free-model.js +67 -0
- package/src/llm/index.js +22 -43
- package/src/llm/prompts.js +45 -33
- package/src/llm/providers/gemini.js +94 -0
- package/src/llm/providers/groq.js +55 -40
- package/src/llm/providers/ollama.js +38 -65
- package/src/llm/providers/openrouter.js +67 -0
- package/src/llm/queue.js +59 -88
- package/src/llm/re-prompter.js +41 -0
- package/src/llm/validator.js +72 -0
- package/src/parser/ast/generic.js +45 -222
- package/src/parser/ast/go.js +86 -205
- package/src/parser/ast/java.js +76 -146
- package/src/parser/ast/javascript.js +173 -241
- package/src/parser/ast/laravel/blade.js +56 -0
- package/src/parser/ast/laravel/classifier.js +30 -0
- package/src/parser/ast/laravel/controller.js +35 -0
- package/src/parser/ast/laravel/index.js +54 -0
- package/src/parser/ast/laravel/model.js +41 -0
- package/src/parser/ast/laravel/provider.js +28 -0
- package/src/parser/ast/laravel/routes.js +45 -0
- package/src/parser/ast/php.js +129 -0
- package/src/parser/ast/python.js +76 -199
- package/src/parser/ast/typescript.js +10 -244
- package/src/parser/body-extractor.js +40 -0
- package/src/parser/call-graph.js +50 -67
- package/src/parser/complexity-scorer.js +59 -0
- package/src/parser/index.js +61 -86
- package/src/parser/pattern-detector.js +71 -0
- package/src/parser/pkg-builder.js +36 -83
- package/src/renderer/html.js +63 -135
- package/src/renderer/index.js +23 -35
- package/src/renderer/json.js +17 -35
- package/src/renderer/markdown.js +83 -117
- package/src/utils/config.js +52 -53
- package/src/utils/errors.js +26 -41
- package/src/utils/logger.js +32 -53
- package/src/cli/flags.js +0 -87
- package/src/llm/providers/anthropic.js +0 -57
- package/src/llm/providers/google.js +0 -65
- package/src/llm/providers/openai.js +0 -52
- package/src/parser/ast/tree-sitter-init.js +0 -80
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Domain pattern detector — scans function body text for known patterns.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const PATTERNS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'arithmetic',
|
|
10
|
+
test: (body) =>
|
|
11
|
+
/[\*\/\%\*\*]/.test(body) || /Math\./.test(body),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'mqtt',
|
|
15
|
+
test: (body) =>
|
|
16
|
+
/\.publish\s*\(/.test(body) ||
|
|
17
|
+
/\.subscribe\s*\(/.test(body) ||
|
|
18
|
+
/\.connect\s*\(/.test(body) ||
|
|
19
|
+
/mqtts?:\/\//.test(body),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'http_call',
|
|
23
|
+
test: (body) =>
|
|
24
|
+
/\bfetch\s*\(/.test(body) ||
|
|
25
|
+
/\baxios\b/.test(body) ||
|
|
26
|
+
/\bhttp\.get\s*\(/.test(body) ||
|
|
27
|
+
/\bcurl\b/.test(body) ||
|
|
28
|
+
/\bGuzzle\b/.test(body),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'database_query',
|
|
32
|
+
test: (body) =>
|
|
33
|
+
/\.query\s*\(/.test(body) ||
|
|
34
|
+
/->where\s*\(/.test(body) ||
|
|
35
|
+
/->select\s*\(/.test(body) ||
|
|
36
|
+
/\bDB::/.test(body) ||
|
|
37
|
+
/\bPDO\b/.test(body) ||
|
|
38
|
+
/\bmongoose\b/.test(body) ||
|
|
39
|
+
/\bprisma\b/.test(body),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'event_emit',
|
|
43
|
+
test: (body) =>
|
|
44
|
+
/\.emit\s*\(/.test(body) ||
|
|
45
|
+
/\.dispatch\s*\(/.test(body) ||
|
|
46
|
+
/\bevent\s*\(/.test(body) ||
|
|
47
|
+
/Event::dispatch\s*\(/.test(body),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'caching',
|
|
51
|
+
test: (body) =>
|
|
52
|
+
/\bcache\s*\(/.test(body) ||
|
|
53
|
+
/\bCache::/.test(body) ||
|
|
54
|
+
/\bRedis::/.test(body),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'queue_job',
|
|
58
|
+
test: (body) =>
|
|
59
|
+
/\bdispatch\s*\(/.test(body) ||
|
|
60
|
+
/Queue::push\s*\(/.test(body) ||
|
|
61
|
+
/->delay\s*\(/.test(body) ||
|
|
62
|
+
/\.queue\s*\(/.test(body),
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
function detectPatterns(bodyText) {
|
|
67
|
+
if (!bodyText) return [];
|
|
68
|
+
return PATTERNS.filter((p) => p.test(bodyText)).map((p) => p.name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { detectPatterns, PATTERNS };
|
|
@@ -1,105 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
* PKG (Project Knowledge Graph) builder.
|
|
3
|
-
* Aggregates all FileFacts into a single ProjectKnowledgeGraph.
|
|
4
|
-
* Infers project name from package.json or directory name.
|
|
5
|
-
* Detects entry points (files with empty calledBy).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
-
import { join, basename } from 'node:path';
|
|
1
|
+
'use strict';
|
|
10
2
|
|
|
11
3
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @property {object} meta
|
|
14
|
-
* @property {string} meta.name
|
|
15
|
-
* @property {string} meta.primaryLanguage
|
|
16
|
-
* @property {number} meta.totalFiles
|
|
17
|
-
* @property {string} meta.analyzedAt
|
|
18
|
-
* @property {Object<string, import('./index.js').FileFacts>} files
|
|
19
|
-
* @property {string[]} entryPoints
|
|
20
|
-
* @property {Object<string, string[]>} graph
|
|
4
|
+
* Assemble all FileFacts into a Project Knowledge Graph (PKG).
|
|
21
5
|
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Build a ProjectKnowledgeGraph from all FileFacts.
|
|
25
|
-
* @param {import('./index.js').FileFacts[]} allFacts
|
|
26
|
-
* @param {string} targetDir - Base directory
|
|
27
|
-
* @returns {ProjectKnowledgeGraph}
|
|
28
|
-
*/
|
|
29
|
-
export function buildPKG(allFacts, targetDir) {
|
|
30
|
-
const name = inferProjectName(targetDir);
|
|
31
|
-
const primaryLanguage = detectPrimaryLanguage(allFacts);
|
|
32
|
-
|
|
6
|
+
function buildPKG(allFacts, meta) {
|
|
33
7
|
const files = {};
|
|
34
8
|
const graph = {};
|
|
9
|
+
const entryPoints = [];
|
|
35
10
|
|
|
36
11
|
for (const facts of allFacts) {
|
|
37
12
|
files[facts.relativePath] = facts;
|
|
38
|
-
|
|
39
|
-
|
|
13
|
+
graph[facts.relativePath] = facts.callsTo || [];
|
|
14
|
+
if (!facts.calledBy || facts.calledBy.length === 0) {
|
|
15
|
+
entryPoints.push(facts.relativePath);
|
|
40
16
|
}
|
|
41
17
|
}
|
|
42
18
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
19
|
+
// Laravel meta aggregation
|
|
20
|
+
let laravelMeta = null;
|
|
21
|
+
if (meta && meta.framework === 'laravel') {
|
|
22
|
+
const routeMap = [];
|
|
23
|
+
const relationships = [];
|
|
24
|
+
const providerBindings = [];
|
|
25
|
+
const modelNames = [];
|
|
26
|
+
|
|
27
|
+
for (const facts of allFacts) {
|
|
28
|
+
if (facts.laravelContext) {
|
|
29
|
+
const ctx = facts.laravelContext;
|
|
30
|
+
if (ctx.type === 'route_file' && ctx.routes) {
|
|
31
|
+
routeMap.push(...ctx.routes);
|
|
32
|
+
}
|
|
33
|
+
if (ctx.type === 'model') {
|
|
34
|
+
if (facts.classes && facts.classes.length > 0) modelNames.push(facts.classes[0].name);
|
|
35
|
+
if (ctx.relationships) relationships.push(...ctx.relationships.map(r => ({ ...r, fromModel: facts.classes && facts.classes[0] ? facts.classes[0].name : facts.relativePath })));
|
|
36
|
+
}
|
|
37
|
+
if (ctx.type === 'provider' && ctx.registerBindings) {
|
|
38
|
+
providerBindings.push({ provider: facts.relativePath, bindings: ctx.registerBindings });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
laravelMeta = { routeMap, relationships, providerBindings, modelNames };
|
|
44
|
+
}
|
|
47
45
|
|
|
48
46
|
return {
|
|
49
47
|
meta: {
|
|
50
|
-
|
|
51
|
-
primaryLanguage,
|
|
48
|
+
...meta,
|
|
52
49
|
totalFiles: allFacts.length,
|
|
53
|
-
analyzedAt: new Date().toISOString(),
|
|
54
50
|
},
|
|
55
51
|
files,
|
|
56
52
|
entryPoints,
|
|
57
53
|
graph,
|
|
54
|
+
laravelMeta,
|
|
58
55
|
};
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
// Try package.json
|
|
63
|
-
const pkgJsonPath = join(targetDir, 'package.json');
|
|
64
|
-
if (existsSync(pkgJsonPath)) {
|
|
65
|
-
try {
|
|
66
|
-
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
67
|
-
if (pkg.name) return pkg.name;
|
|
68
|
-
} catch {
|
|
69
|
-
// Ignore parse errors
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Try go.mod
|
|
74
|
-
const goModPath = join(targetDir, 'go.mod');
|
|
75
|
-
if (existsSync(goModPath)) {
|
|
76
|
-
try {
|
|
77
|
-
const content = readFileSync(goModPath, 'utf-8');
|
|
78
|
-
const match = content.match(/module\s+(\S+)/);
|
|
79
|
-
if (match) return match[1];
|
|
80
|
-
} catch {
|
|
81
|
-
// Ignore
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Fall back to directory name
|
|
86
|
-
return basename(targetDir);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function detectPrimaryLanguage(allFacts) {
|
|
90
|
-
const counts = {};
|
|
91
|
-
for (const facts of allFacts) {
|
|
92
|
-
counts[facts.language] = (counts[facts.language] || 0) + 1;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let maxLang = 'unknown';
|
|
96
|
-
let maxCount = 0;
|
|
97
|
-
for (const [lang, count] of Object.entries(counts)) {
|
|
98
|
-
if (count > maxCount) {
|
|
99
|
-
maxLang = lang;
|
|
100
|
-
maxCount = count;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return maxLang;
|
|
105
|
-
}
|
|
58
|
+
module.exports = { buildPKG };
|
package/src/renderer/html.js
CHANGED
|
@@ -1,152 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
* HTML renderer.
|
|
3
|
-
* Converts all Markdown fragments to HTML using marked.
|
|
4
|
-
* Injects sidebar navigation.
|
|
5
|
-
* Outputs single self-contained index.html.
|
|
6
|
-
*/
|
|
1
|
+
'use strict';
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
const { writeFileSync, mkdirSync } = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { marked } = require('marked');
|
|
11
6
|
|
|
12
7
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {import('../llm/queue.js').DocFragment[]} fragments
|
|
15
|
-
* @param {import('../parser/pkg-builder.js').ProjectKnowledgeGraph} pkg
|
|
16
|
-
* @param {string} outputDir - Output directory path
|
|
8
|
+
* HTML renderer — single self-contained index.html with lunr.js search.
|
|
17
9
|
*/
|
|
18
|
-
|
|
10
|
+
async function render(fragments, pkg, outputDir, config) {
|
|
19
11
|
mkdirSync(outputDir, { recursive: true });
|
|
20
12
|
|
|
21
|
-
const
|
|
22
|
-
|
|
13
|
+
const nav = fragments.map(f => {
|
|
14
|
+
const id = f.relativePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
15
|
+
return `<li><a href="#${id}">${f.relativePath}</a></li>`;
|
|
16
|
+
}).join('\n');
|
|
17
|
+
|
|
18
|
+
const content = fragments.map(f => {
|
|
19
|
+
const id = f.relativePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
20
|
+
const html = marked(f.content || '');
|
|
21
|
+
return `<section id="${id}"><h2>${f.relativePath}</h2>${html}</section>`;
|
|
22
|
+
}).join('\n');
|
|
23
|
+
|
|
24
|
+
const lunrDocs = fragments.map(f => ({
|
|
25
|
+
id: f.relativePath,
|
|
26
|
+
title: f.relativePath,
|
|
27
|
+
body: (f.content || '').replace(/[#`*_\[\]()]/g, ' '),
|
|
28
|
+
}));
|
|
23
29
|
|
|
24
30
|
const html = `<!DOCTYPE html>
|
|
25
31
|
<html lang="en">
|
|
26
32
|
<head>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.sidebar .dir-label { color: #6c7086; font-size: 11px; text-transform: uppercase; margin-top: 12px; margin-bottom: 4px; padding-left: 8px; }
|
|
44
|
-
|
|
45
|
-
.search-box { width: 100%; padding: 8px 12px; background: #313244; border: 1px solid #45475a; border-radius: 6px; color: #cdd6f4; font-size: 13px; margin-bottom: 16px; }
|
|
46
|
-
.search-box::placeholder { color: #6c7086; }
|
|
47
|
-
|
|
48
|
-
.content { margin-left: 280px; padding: 40px; max-width: 900px; flex: 1; }
|
|
49
|
-
.content h1 { color: #1e1e2e; border-bottom: 2px solid #89b4fa; padding-bottom: 8px; margin-bottom: 24px; }
|
|
50
|
-
.content h2 { color: #313244; margin-top: 32px; margin-bottom: 12px; }
|
|
51
|
-
.content h3 { color: #45475a; margin-top: 20px; margin-bottom: 8px; }
|
|
52
|
-
.content p { line-height: 1.6; margin-bottom: 12px; color: #313244; }
|
|
53
|
-
.content code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 14px; }
|
|
54
|
-
.content pre { background: #1e1e2e; color: #cdd6f4; padding: 16px; border-radius: 8px; overflow-x: auto; margin-bottom: 16px; }
|
|
55
|
-
.content pre code { background: none; padding: 0; }
|
|
56
|
-
.content table { border-collapse: collapse; width: 100%; margin-bottom: 16px; }
|
|
57
|
-
.content th, .content td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
|
58
|
-
.content th { background: #f5f5f5; }
|
|
59
|
-
.content ul, .content ol { margin-left: 24px; margin-bottom: 12px; }
|
|
60
|
-
.content li { margin-bottom: 4px; line-height: 1.5; }
|
|
61
|
-
|
|
62
|
-
.file-section { border-top: 1px solid #e0e0e0; padding-top: 32px; margin-top: 32px; }
|
|
63
|
-
.file-section:first-child { border-top: none; padding-top: 0; margin-top: 0; }
|
|
64
|
-
|
|
65
|
-
.meta-table { font-size: 14px; }
|
|
66
|
-
.meta-table td:first-child { font-weight: 600; width: 150px; }
|
|
67
|
-
</style>
|
|
33
|
+
<meta charset="UTF-8">
|
|
34
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
35
|
+
<title>${pkg.meta && pkg.meta.name || 'Legacyver'} Documentation</title>
|
|
36
|
+
<script src="https://unpkg.com/lunr/lunr.js"></script>
|
|
37
|
+
<style>
|
|
38
|
+
body { font-family: system-ui, sans-serif; display: flex; margin: 0; }
|
|
39
|
+
nav { width: 250px; min-height: 100vh; background: #1e1e2e; color: #cdd6f4; padding: 1rem; overflow-y: auto; position: sticky; top: 0; }
|
|
40
|
+
nav ul { list-style: none; padding: 0; }
|
|
41
|
+
nav a { color: #89b4fa; text-decoration: none; font-size: 0.85rem; }
|
|
42
|
+
nav a:hover { color: #cba6f7; }
|
|
43
|
+
main { flex: 1; padding: 2rem; max-width: 900px; }
|
|
44
|
+
pre { background: #1e1e2e; color: #cdd6f4; padding: 1rem; border-radius: 6px; overflow-x: auto; }
|
|
45
|
+
code { background: #313244; padding: 2px 4px; border-radius: 3px; }
|
|
46
|
+
#search { width: 100%; padding: 0.5rem; margin-bottom: 1rem; background: #313244; border: none; color: #cdd6f4; border-radius: 4px; }
|
|
47
|
+
#search-results { background: #313244; padding: 0.5rem; border-radius: 4px; margin-bottom: 1rem; display: none; }
|
|
48
|
+
</style>
|
|
68
49
|
</head>
|
|
69
50
|
<body>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
94
|
-
</script>
|
|
51
|
+
<nav>
|
|
52
|
+
<input id="search" type="search" placeholder="Search..." />
|
|
53
|
+
<div id="search-results"></div>
|
|
54
|
+
<ul>${nav}</ul>
|
|
55
|
+
</nav>
|
|
56
|
+
<main>${content}</main>
|
|
57
|
+
<script>
|
|
58
|
+
const docs = ${JSON.stringify(lunrDocs)};
|
|
59
|
+
const idx = lunr(function() {
|
|
60
|
+
this.field('title'); this.field('body');
|
|
61
|
+
docs.forEach(d => this.add(d));
|
|
62
|
+
});
|
|
63
|
+
document.getElementById('search').addEventListener('input', function() {
|
|
64
|
+
const q = this.value.trim();
|
|
65
|
+
const res = document.getElementById('search-results');
|
|
66
|
+
if (!q) { res.style.display = 'none'; return; }
|
|
67
|
+
try {
|
|
68
|
+
const results = idx.search(q);
|
|
69
|
+
res.innerHTML = results.map(r => '<div><a href="#' + r.ref.replace(/[^a-zA-Z0-9]/g,'-') + '">' + r.ref + '</a></div>').join('') || '<div>No results</div>';
|
|
70
|
+
res.style.display = 'block';
|
|
71
|
+
} catch(e) { res.style.display = 'none'; }
|
|
72
|
+
});
|
|
73
|
+
</script>
|
|
95
74
|
</body>
|
|
96
75
|
</html>`;
|
|
97
76
|
|
|
98
|
-
writeFileSync(join(outputDir, 'index.html'), html, '
|
|
77
|
+
writeFileSync(path.join(outputDir, 'index.html'), html, 'utf8');
|
|
99
78
|
}
|
|
100
79
|
|
|
101
|
-
|
|
102
|
-
const dirs = new Map();
|
|
103
|
-
|
|
104
|
-
for (const fragment of fragments) {
|
|
105
|
-
const dir = dirname(fragment.relativePath);
|
|
106
|
-
if (!dirs.has(dir)) {
|
|
107
|
-
dirs.set(dir, []);
|
|
108
|
-
}
|
|
109
|
-
dirs.get(dir).push(fragment);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let html = '<ul>';
|
|
113
|
-
|
|
114
|
-
const sortedDirs = [...dirs.keys()].sort();
|
|
115
|
-
for (const dir of sortedDirs) {
|
|
116
|
-
if (dir !== '.') {
|
|
117
|
-
html += `<li><div class="dir-label">${dir}</div></li>`;
|
|
118
|
-
}
|
|
119
|
-
const files = dirs.get(dir).sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
120
|
-
for (const f of files) {
|
|
121
|
-
const id = f.relativePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
122
|
-
html += `<li><a href="#${id}">${f.relativePath}</a></li>`;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
html += '</ul>';
|
|
127
|
-
return html;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function buildContent(fragments, pkg) {
|
|
131
|
-
let html = '';
|
|
132
|
-
|
|
133
|
-
// Project overview
|
|
134
|
-
html += `<h1>${pkg.meta.name} Documentation</h1>`;
|
|
135
|
-
html += `<table class="meta-table">`;
|
|
136
|
-
html += `<tr><td>Primary Language</td><td>${pkg.meta.primaryLanguage}</td></tr>`;
|
|
137
|
-
html += `<tr><td>Total Files</td><td>${pkg.meta.totalFiles}</td></tr>`;
|
|
138
|
-
html += `<tr><td>Analyzed At</td><td>${pkg.meta.analyzedAt}</td></tr>`;
|
|
139
|
-
html += `</table>`;
|
|
140
|
-
|
|
141
|
-
// Individual file docs
|
|
142
|
-
const sorted = [...fragments].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
143
|
-
for (const fragment of sorted) {
|
|
144
|
-
const id = fragment.relativePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
145
|
-
html += `<div class="file-section" id="${id}">`;
|
|
146
|
-
html += `<h2>${fragment.relativePath}</h2>`;
|
|
147
|
-
html += marked(fragment.content);
|
|
148
|
-
html += `</div>`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return html;
|
|
152
|
-
}
|
|
80
|
+
module.exports = { render };
|
package/src/renderer/index.js
CHANGED
|
@@ -1,44 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
* Renderer dispatcher.
|
|
3
|
-
* Reads format from config, calls correct renderer.
|
|
4
|
-
* Ensures output directory exists.
|
|
5
|
-
*/
|
|
1
|
+
'use strict';
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { renderHTML } from './html.js';
|
|
10
|
-
import { renderJSON } from './json.js';
|
|
11
|
-
import { RenderError } from '../utils/errors.js';
|
|
12
|
-
|
|
13
|
-
const RENDERERS = {
|
|
14
|
-
markdown: renderMarkdown,
|
|
15
|
-
html: renderHTML,
|
|
16
|
-
json: renderJSON,
|
|
17
|
-
};
|
|
3
|
+
const { mkdirSync } = require('fs');
|
|
4
|
+
const { RenderError } = require('../utils/errors');
|
|
18
5
|
|
|
19
6
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @param {import('../llm/queue.js').DocFragment[]} fragments
|
|
22
|
-
* @param {import('../parser/pkg-builder.js').ProjectKnowledgeGraph} pkg
|
|
23
|
-
* @param {object} options
|
|
24
|
-
* @param {string} options.format - Output format (markdown, html, json)
|
|
25
|
-
* @param {string} options.outputDir - Output directory
|
|
7
|
+
* Renderer dispatcher.
|
|
26
8
|
*/
|
|
27
|
-
|
|
28
|
-
const { format = 'markdown', outputDir } = options;
|
|
29
|
-
|
|
30
|
-
const renderer = RENDERERS[format];
|
|
31
|
-
if (!renderer) {
|
|
32
|
-
throw new RenderError(format, `Unsupported format "${format}". Supported: ${Object.keys(RENDERERS).join(', ')}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Ensure output directory exists
|
|
9
|
+
async function render(fragments, pkg, outputDir, config) {
|
|
36
10
|
mkdirSync(outputDir, { recursive: true });
|
|
37
11
|
|
|
12
|
+
const format = (config.format || 'markdown').toLowerCase();
|
|
13
|
+
|
|
38
14
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
15
|
+
switch (format) {
|
|
16
|
+
case 'html':
|
|
17
|
+
await require('./html').render(fragments, pkg, outputDir, config);
|
|
18
|
+
break;
|
|
19
|
+
case 'json':
|
|
20
|
+
await require('./json').render(fragments, pkg, outputDir, config);
|
|
21
|
+
break;
|
|
22
|
+
case 'markdown':
|
|
23
|
+
default:
|
|
24
|
+
await require('./markdown').render(fragments, pkg, outputDir, config);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new RenderError(format, e);
|
|
43
29
|
}
|
|
44
30
|
}
|
|
31
|
+
|
|
32
|
+
module.exports = { render };
|
package/src/renderer/json.js
CHANGED
|
@@ -1,46 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
* JSON renderer.
|
|
3
|
-
* Outputs full PKG with llmDescription field added to each file entry.
|
|
4
|
-
* Writes legacyver-docs/documentation.json.
|
|
5
|
-
*/
|
|
1
|
+
'use strict';
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const { writeFileSync, mkdirSync } = require('fs');
|
|
4
|
+
const path = require('path');
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {import('../llm/queue.js').DocFragment[]} fragments
|
|
13
|
-
* @param {import('../parser/pkg-builder.js').ProjectKnowledgeGraph} pkg
|
|
14
|
-
* @param {string} outputDir - Output directory path
|
|
7
|
+
* JSON renderer — outputs PKG enriched with LLM descriptions.
|
|
15
8
|
*/
|
|
16
|
-
|
|
9
|
+
async function render(fragments, pkg, outputDir, config) {
|
|
17
10
|
mkdirSync(outputDir, { recursive: true });
|
|
18
11
|
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
for (const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const output = {
|
|
27
|
-
meta: pkg.meta,
|
|
28
|
-
entryPoints: pkg.entryPoints,
|
|
29
|
-
graph: pkg.graph,
|
|
30
|
-
files: {},
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
for (const [filePath, facts] of Object.entries(pkg.files)) {
|
|
34
|
-
const fragment = fragmentMap.get(filePath);
|
|
35
|
-
output.files[filePath] = {
|
|
36
|
-
...facts,
|
|
37
|
-
llmDescription: fragment ? fragment.content : null,
|
|
38
|
-
};
|
|
12
|
+
// Enrich PKG with LLM descriptions
|
|
13
|
+
const enrichedPkg = JSON.parse(JSON.stringify(pkg));
|
|
14
|
+
for (const frag of fragments) {
|
|
15
|
+
if (enrichedPkg.files[frag.relativePath]) {
|
|
16
|
+
enrichedPkg.files[frag.relativePath].llmDescription = frag.content;
|
|
17
|
+
enrichedPkg.files[frag.relativePath]._qualityWarnings = frag._qualityWarnings || [];
|
|
18
|
+
}
|
|
39
19
|
}
|
|
40
20
|
|
|
41
21
|
writeFileSync(
|
|
42
|
-
join(outputDir, 'documentation.json'),
|
|
43
|
-
JSON.stringify(
|
|
44
|
-
'
|
|
22
|
+
path.join(outputDir, 'documentation.json'),
|
|
23
|
+
JSON.stringify(enrichedPkg, null, 2),
|
|
24
|
+
'utf8'
|
|
45
25
|
);
|
|
46
26
|
}
|
|
27
|
+
|
|
28
|
+
module.exports = { render };
|