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
package/src/renderer/markdown.js
CHANGED
|
@@ -1,149 +1,115 @@
|
|
|
1
|
-
|
|
2
|
-
* Markdown renderer.
|
|
3
|
-
* Takes DocFragment[] and PKG.
|
|
4
|
-
* Mirrors source directory structure in output dir.
|
|
5
|
-
* Writes one .md per file.
|
|
6
|
-
* Generates index.md with project metadata + Mermaid dependency diagram + module index.
|
|
7
|
-
* Generates SUMMARY.md in GitBook format.
|
|
8
|
-
*/
|
|
1
|
+
'use strict';
|
|
9
2
|
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
const { writeFileSync, mkdirSync, existsSync } = require('fs');
|
|
4
|
+
const path = require('path');
|
|
12
5
|
|
|
13
6
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {import('../llm/queue.js').DocFragment[]} fragments
|
|
16
|
-
* @param {import('../parser/pkg-builder.js').ProjectKnowledgeGraph} pkg
|
|
17
|
-
* @param {string} outputDir - Output directory path
|
|
7
|
+
* Markdown renderer — produces one .md per source file + index.md + SUMMARY.md.
|
|
18
8
|
*/
|
|
19
|
-
|
|
9
|
+
async function render(fragments, pkg, outputDir, config) {
|
|
20
10
|
mkdirSync(outputDir, { recursive: true });
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
const summaryLines = ['# Summary\n'];
|
|
13
|
+
|
|
14
|
+
for (const frag of fragments) {
|
|
15
|
+
const relOut = frag.relativePath.replace(/\.[^.]+$/, '.md');
|
|
16
|
+
const outPath = path.join(outputDir, relOut);
|
|
17
|
+
mkdirSync(path.dirname(outPath), { recursive: true });
|
|
18
|
+
writeFileSync(outPath, frag.content, 'utf8');
|
|
19
|
+
summaryLines.push(`* [${frag.relativePath}](${relOut})`);
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
writeFileSync(join(outputDir, 'index.md'), indexContent, 'utf-8');
|
|
22
|
+
// SUMMARY.md (GitBook / Docusaurus compatible)
|
|
23
|
+
writeFileSync(path.join(outputDir, 'SUMMARY.md'), summaryLines.join('\n'), 'utf8');
|
|
32
24
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
writeFileSync(join(outputDir, 'SUMMARY.md'), summaryContent, 'utf-8');
|
|
25
|
+
// index.md
|
|
26
|
+
writeFileSync(path.join(outputDir, 'index.md'), buildIndexMd(pkg, fragments), 'utf8');
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
function
|
|
29
|
+
function buildIndexMd(pkg, fragments) {
|
|
30
|
+
const meta = pkg.meta || {};
|
|
39
31
|
const lines = [];
|
|
40
|
-
|
|
41
|
-
lines.push(`# ${pkg.meta.name}`);
|
|
32
|
+
lines.push(`# ${meta.name || 'Project'} — Documentation`);
|
|
42
33
|
lines.push('');
|
|
43
|
-
lines.push(
|
|
34
|
+
lines.push(`**Primary language:** ${meta.primaryLanguage || 'unknown'} `);
|
|
35
|
+
lines.push(`**Total files:** ${meta.totalFiles || fragments.length} `);
|
|
36
|
+
lines.push(`**Analyzed at:** ${meta.analyzedAt || new Date().toISOString()} `);
|
|
37
|
+
if (meta.framework) lines.push(`**Framework:** ${meta.framework} `);
|
|
44
38
|
lines.push('');
|
|
45
|
-
|
|
39
|
+
|
|
40
|
+
// File tree
|
|
41
|
+
lines.push('## Files');
|
|
46
42
|
lines.push('');
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
lines.push(`| Analyzed At | ${pkg.meta.analyzedAt} |`);
|
|
43
|
+
for (const frag of fragments) {
|
|
44
|
+
const relOut = frag.relativePath.replace(/\.[^.]+$/, '.md');
|
|
45
|
+
lines.push(`- [${frag.relativePath}](${relOut})`);
|
|
46
|
+
}
|
|
52
47
|
lines.push('');
|
|
53
48
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
// Mermaid dependency graph
|
|
50
|
+
lines.push('## Dependency Graph');
|
|
51
|
+
lines.push('');
|
|
52
|
+
lines.push('```mermaid');
|
|
53
|
+
lines.push('graph TD');
|
|
54
|
+
const graph = pkg.graph || {};
|
|
55
|
+
for (const [from, targets] of Object.entries(graph)) {
|
|
56
|
+
for (const to of targets) {
|
|
57
|
+
const fromId = sanitizeMermaid(from);
|
|
58
|
+
const toId = sanitizeMermaid(to);
|
|
59
|
+
lines.push(` ${fromId}["${from}"] --> ${toId}["${to}"]`);
|
|
61
60
|
}
|
|
62
|
-
lines.push('');
|
|
63
61
|
}
|
|
62
|
+
lines.push('```');
|
|
63
|
+
lines.push('');
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!nodeIds.has(path)) {
|
|
77
|
-
nodeIds.set(path, `N${nodeCounter++}`);
|
|
65
|
+
// Laravel-specific sections
|
|
66
|
+
if (pkg.laravelMeta) {
|
|
67
|
+
const lm = pkg.laravelMeta;
|
|
68
|
+
|
|
69
|
+
if (lm.routeMap && lm.routeMap.length > 0) {
|
|
70
|
+
lines.push('## Route Map');
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push('| Method | URI | Controller | Middleware | Route Name |');
|
|
73
|
+
lines.push('|--------|-----|------------|------------|------------|');
|
|
74
|
+
for (const r of lm.routeMap) {
|
|
75
|
+
lines.push(`| ${r.method || ''} | ${r.uri || ''} | ${r.controller || ''}${r.action ? '@' + r.action : ''} | ${r.middleware || ''} | ${r.name || ''} |`);
|
|
78
76
|
}
|
|
79
|
-
|
|
77
|
+
lines.push('');
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
if (lm.relationships && lm.relationships.length > 0) {
|
|
81
|
+
lines.push('## Model Relationships');
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push('```mermaid');
|
|
84
|
+
lines.push('erDiagram');
|
|
85
|
+
for (const rel of lm.relationships) {
|
|
86
|
+
const from = rel.fromModel || 'Model';
|
|
87
|
+
const to = rel.relatedModel || 'Related';
|
|
88
|
+
const label = rel.type || 'relates';
|
|
89
|
+
lines.push(` ${from} ||--o{ ${to} : "${label}"`);
|
|
87
90
|
}
|
|
91
|
+
lines.push('```');
|
|
92
|
+
lines.push('');
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for (const filePath of sortedPaths) {
|
|
102
|
-
const facts = pkg.files[filePath];
|
|
103
|
-
const mdLink = filePath.replace(/\.\w+$/, '.md');
|
|
104
|
-
lines.push(
|
|
105
|
-
`| [${filePath}](${mdLink}) | ${facts.language} | ${facts.linesOfCode} | ${facts.functions.length} | ${facts.classes.length} |`
|
|
106
|
-
);
|
|
95
|
+
if (lm.providerBindings && lm.providerBindings.length > 0) {
|
|
96
|
+
lines.push('## Service Provider Bindings');
|
|
97
|
+
lines.push('');
|
|
98
|
+
for (const pb of lm.providerBindings) {
|
|
99
|
+
lines.push(`### ${pb.provider}`);
|
|
100
|
+
for (const b of pb.bindings) {
|
|
101
|
+
lines.push(`- \`${b}\``);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
lines.push('');
|
|
110
|
-
lines.push('---');
|
|
111
|
-
lines.push('*Generated by [Legacyver](https://github.com/legacyver/legacyver)*');
|
|
112
|
-
lines.push('');
|
|
113
|
-
|
|
114
108
|
return lines.join('\n');
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
lines.push('# Summary');
|
|
121
|
-
lines.push('');
|
|
122
|
-
lines.push('* [Index](index.md)');
|
|
123
|
-
|
|
124
|
-
// Group by directory
|
|
125
|
-
const dirs = new Map();
|
|
126
|
-
for (const fragment of fragments) {
|
|
127
|
-
const dir = dirname(fragment.relativePath);
|
|
128
|
-
if (!dirs.has(dir)) {
|
|
129
|
-
dirs.set(dir, []);
|
|
130
|
-
}
|
|
131
|
-
dirs.get(dir).push(fragment);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const sortedDirs = [...dirs.keys()].sort();
|
|
135
|
-
for (const dir of sortedDirs) {
|
|
136
|
-
if (dir !== '.') {
|
|
137
|
-
lines.push(`* ${dir}`);
|
|
138
|
-
}
|
|
139
|
-
const files = dirs.get(dir).sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
140
|
-
for (const f of files) {
|
|
141
|
-
const mdPath = f.relativePath.replace(/\.\w+$/, '.md');
|
|
142
|
-
const indent = dir !== '.' ? ' ' : '';
|
|
143
|
-
lines.push(`${indent}* [${f.relativePath}](${mdPath})`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
lines.push('');
|
|
148
|
-
return lines.join('\n');
|
|
111
|
+
function sanitizeMermaid(str) {
|
|
112
|
+
return str.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^_+/, '').slice(0, 40) || 'node';
|
|
149
113
|
}
|
|
114
|
+
|
|
115
|
+
module.exports = { render };
|
package/src/utils/config.js
CHANGED
|
@@ -1,65 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
* Configuration loader using cosmiconfig.
|
|
3
|
-
* Loads .legacyverrc / legacyver.config.js / legacyver.config.yaml
|
|
4
|
-
* Merges with CLI flags (CLI wins).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { cosmiconfig } from 'cosmiconfig';
|
|
1
|
+
'use strict';
|
|
8
2
|
|
|
9
|
-
const
|
|
10
|
-
provider: 'anthropic',
|
|
11
|
-
model: null,
|
|
12
|
-
concurrency: 5,
|
|
13
|
-
maxFileSizeKb: 500,
|
|
14
|
-
output: './legacyver-docs',
|
|
15
|
-
format: 'markdown',
|
|
16
|
-
incremental: false,
|
|
17
|
-
noConfirm: false,
|
|
18
|
-
verbose: false,
|
|
19
|
-
languages: ['javascript', 'typescript', 'python', 'java', 'go'],
|
|
20
|
-
ignore: [],
|
|
21
|
-
};
|
|
3
|
+
const { cosmiconfigSync } = require('cosmiconfig');
|
|
22
4
|
|
|
23
|
-
|
|
5
|
+
const explorer = cosmiconfigSync('legacyver', {
|
|
6
|
+
searchPlaces: [
|
|
7
|
+
'.legacyverrc',
|
|
8
|
+
'.legacyverrc.json',
|
|
9
|
+
'.legacyverrc.yaml',
|
|
10
|
+
'.legacyverrc.yml',
|
|
11
|
+
'.legacyverrc.js',
|
|
12
|
+
'legacyver.config.js',
|
|
13
|
+
'legacyver.config.yaml',
|
|
14
|
+
'legacyver.config.yml',
|
|
15
|
+
],
|
|
16
|
+
});
|
|
24
17
|
|
|
25
18
|
/**
|
|
26
|
-
* Load configuration from
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
19
|
+
* Load configuration from file and merge with CLI flags.
|
|
20
|
+
* CLI flags always win over file config.
|
|
21
|
+
* @param {Object} cliFlags
|
|
22
|
+
* @returns {Object}
|
|
29
23
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
function loadConfig(cliFlags = {}) {
|
|
25
|
+
let fileConfig = {};
|
|
26
|
+
try {
|
|
27
|
+
const result = explorer.search();
|
|
28
|
+
if (result && result.config) {
|
|
29
|
+
fileConfig = { ...result.config }; // shallow copy — never mutate the cached object
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// no config file found — use defaults
|
|
33
|
+
}
|
|
32
34
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
+
const defaults = {
|
|
36
|
+
provider: 'openrouter',
|
|
37
|
+
model: undefined,
|
|
38
|
+
format: 'markdown',
|
|
39
|
+
out: './legacyver-docs',
|
|
40
|
+
concurrency: 3,
|
|
41
|
+
maxFileSizeKb: 500,
|
|
42
|
+
dryRun: false,
|
|
43
|
+
incremental: false,
|
|
44
|
+
confirm: true,
|
|
45
|
+
verbose: false,
|
|
46
|
+
};
|
|
35
47
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
// Merge: defaults < fileConfig < cliFlags (strip undefined cliFlags)
|
|
49
|
+
const cleanCli = Object.fromEntries(
|
|
50
|
+
Object.entries(cliFlags).filter(([, v]) => v !== undefined)
|
|
51
|
+
);
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*/
|
|
48
|
-
export function mergeWithFlags(config, cliFlags) {
|
|
49
|
-
const merged = { ...config };
|
|
50
|
-
for (const [key, value] of Object.entries(cliFlags)) {
|
|
51
|
-
if (value !== undefined && value !== null) {
|
|
52
|
-
merged[key] = value;
|
|
53
|
-
}
|
|
53
|
+
// If CLI specifies a different provider than what's in the config file,
|
|
54
|
+
// discard the file's model — it belongs to the old provider.
|
|
55
|
+
const effectiveProvider = cleanCli.provider || fileConfig.provider || defaults.provider;
|
|
56
|
+
const fileConfigProvider = fileConfig.provider || defaults.provider;
|
|
57
|
+
if (effectiveProvider !== fileConfigProvider && !cleanCli.model) {
|
|
58
|
+
delete fileConfig.model;
|
|
54
59
|
}
|
|
55
|
-
return merged;
|
|
56
|
-
}
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
* Reset cached config (for testing).
|
|
60
|
-
*/
|
|
61
|
-
export function resetConfig() {
|
|
62
|
-
cachedConfig = null;
|
|
61
|
+
return { ...defaults, ...fileConfig, ...cleanCli };
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
module.exports = { loadConfig };
|
package/src/utils/errors.js
CHANGED
|
@@ -1,70 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Custom error classes for Legacyver.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
constructor(message,
|
|
7
|
+
class LegacyverError extends Error {
|
|
8
|
+
constructor(message, code) {
|
|
7
9
|
super(message);
|
|
8
10
|
this.name = 'LegacyverError';
|
|
9
|
-
this.
|
|
10
|
-
this.suggestion = options.suggestion || null;
|
|
11
|
+
this.code = code || 'LEGACYVER_ERROR';
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
class NoApiKeyError extends LegacyverError {
|
|
15
16
|
constructor(provider) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
const envVar = envVarMap[provider] || `${provider.toUpperCase()}_API_KEY`;
|
|
24
|
-
const suggestion = provider === 'ollama'
|
|
25
|
-
? 'Ensure Ollama is running locally at http://localhost:11434'
|
|
26
|
-
: `Set the ${envVar} environment variable or run "legacyver init" to configure`;
|
|
27
|
-
|
|
28
|
-
super(`No API key found for provider "${provider}"`, {
|
|
29
|
-
exitCode: 1,
|
|
30
|
-
suggestion,
|
|
31
|
-
});
|
|
17
|
+
super(
|
|
18
|
+
`No API key found for provider "${provider}". ` +
|
|
19
|
+
`Set the OPENROUTER_API_KEY environment variable or run \`legacyver init\`. ` +
|
|
20
|
+
`Get a key at https://openrouter.ai/keys`,
|
|
21
|
+
'NO_API_KEY'
|
|
22
|
+
);
|
|
32
23
|
this.name = 'NoApiKeyError';
|
|
33
24
|
this.provider = provider;
|
|
34
|
-
this.envVar = envVar;
|
|
35
25
|
}
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
constructor(provider,
|
|
40
|
-
super(`Rate limit exceeded for provider "${provider}"
|
|
41
|
-
exitCode: 2,
|
|
42
|
-
suggestion: 'Reduce concurrency with --concurrency flag or wait and retry',
|
|
43
|
-
});
|
|
28
|
+
class RateLimitError extends LegacyverError {
|
|
29
|
+
constructor(provider, retryAfter) {
|
|
30
|
+
super(`Rate limit exceeded for provider "${provider}". Retrying...`, 'RATE_LIMIT');
|
|
44
31
|
this.name = 'RateLimitError';
|
|
45
32
|
this.provider = provider;
|
|
46
|
-
this.
|
|
33
|
+
this.retryAfter = retryAfter || 1000;
|
|
47
34
|
}
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
constructor(filePath,
|
|
52
|
-
super(`Failed to parse "${filePath}": ${
|
|
53
|
-
exitCode: 0, // recoverable — don't exit
|
|
54
|
-
suggestion: 'File will be processed with the generic fallback parser',
|
|
55
|
-
});
|
|
37
|
+
class ParseError extends LegacyverError {
|
|
38
|
+
constructor(filePath, originalError) {
|
|
39
|
+
super(`Failed to parse file "${filePath}": ${originalError && originalError.message || originalError}`, 'PARSE_ERROR');
|
|
56
40
|
this.name = 'ParseError';
|
|
57
41
|
this.filePath = filePath;
|
|
42
|
+
this.originalError = originalError;
|
|
58
43
|
}
|
|
59
44
|
}
|
|
60
45
|
|
|
61
|
-
|
|
62
|
-
constructor(format,
|
|
63
|
-
super(`
|
|
64
|
-
exitCode: 3,
|
|
65
|
-
suggestion: 'Check write permissions on the output directory',
|
|
66
|
-
});
|
|
46
|
+
class RenderError extends LegacyverError {
|
|
47
|
+
constructor(format, originalError) {
|
|
48
|
+
super(`Renderer failed for format "${format}": ${originalError && originalError.message || originalError}`, 'RENDER_ERROR');
|
|
67
49
|
this.name = 'RenderError';
|
|
68
50
|
this.format = format;
|
|
51
|
+
this.originalError = originalError;
|
|
69
52
|
}
|
|
70
53
|
}
|
|
54
|
+
|
|
55
|
+
module.exports = { LegacyverError, NoApiKeyError, RateLimitError, ParseError, RenderError };
|
package/src/utils/logger.js
CHANGED
|
@@ -1,67 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
* Structured logger with log levels and TTY detection for CI mode.
|
|
3
|
-
*/
|
|
1
|
+
'use strict';
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
const pc = require('picocolors');
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
const isTTY = process.stdout.isTTY === true;
|
|
5
|
+
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
7
|
+
let currentLevel = 'info';
|
|
8
|
+
let isCI = !process.stdout.isTTY;
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
function setLevel(level) {
|
|
11
|
+
if (LOG_LEVELS[level] !== undefined) currentLevel = level;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
function setCI(val) {
|
|
15
|
+
isCI = val;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
function
|
|
23
|
-
return
|
|
18
|
+
function shouldLog(level) {
|
|
19
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.log(`[${timestamp()}] INFO: ${message}`, ...args);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
22
|
+
function debug(...args) {
|
|
23
|
+
if (shouldLog('debug')) {
|
|
24
|
+
console.debug(pc.gray('[debug]'), ...args);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
},
|
|
28
|
+
function info(...args) {
|
|
29
|
+
if (shouldLog('info')) {
|
|
30
|
+
console.log(pc.cyan('[info]'), ...args);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
},
|
|
34
|
+
function warn(...args) {
|
|
35
|
+
if (shouldLog('warn')) {
|
|
36
|
+
console.warn(pc.yellow('[warn]'), ...args);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(`[${timestamp()}] DEBUG: ${message}`, ...args);
|
|
57
|
-
}
|
|
58
|
-
},
|
|
40
|
+
function error(...args) {
|
|
41
|
+
if (shouldLog('error')) {
|
|
42
|
+
console.error(pc.red('[error]'), ...args);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
59
45
|
|
|
60
|
-
|
|
61
|
-
if (isTTY) {
|
|
62
|
-
console.log(picocolors.green('✔'), message, ...args);
|
|
63
|
-
} else {
|
|
64
|
-
console.log(`[${timestamp()}] OK: ${message}`, ...args);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
};
|
|
46
|
+
module.exports = { debug, info, warn, error, setLevel, setCI };
|
package/src/cli/flags.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CLI flag definitions with types, defaults, and descriptions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const FLAG_DEFINITIONS = {
|
|
6
|
-
provider: {
|
|
7
|
-
flags: '-p, --provider <name>',
|
|
8
|
-
description: 'LLM provider to use (anthropic, openai, google, groq, ollama)',
|
|
9
|
-
default: 'anthropic',
|
|
10
|
-
},
|
|
11
|
-
model: {
|
|
12
|
-
flags: '-m, --model <name>',
|
|
13
|
-
description: 'Model name to use (overrides provider default)',
|
|
14
|
-
default: undefined,
|
|
15
|
-
},
|
|
16
|
-
output: {
|
|
17
|
-
flags: '-o, --output <dir>',
|
|
18
|
-
description: 'Output directory for generated documentation',
|
|
19
|
-
default: './legacyver-docs',
|
|
20
|
-
},
|
|
21
|
-
format: {
|
|
22
|
-
flags: '-f, --format <type>',
|
|
23
|
-
description: 'Output format: markdown, html, json',
|
|
24
|
-
default: 'markdown',
|
|
25
|
-
},
|
|
26
|
-
concurrency: {
|
|
27
|
-
flags: '-c, --concurrency <number>',
|
|
28
|
-
description: 'Number of concurrent LLM requests',
|
|
29
|
-
default: '5',
|
|
30
|
-
parseArg: (val) => parseInt(val, 10),
|
|
31
|
-
},
|
|
32
|
-
maxFileSize: {
|
|
33
|
-
flags: '--max-file-size <kb>',
|
|
34
|
-
description: 'Skip files larger than this size in KB',
|
|
35
|
-
default: '500',
|
|
36
|
-
parseArg: (val) => parseInt(val, 10),
|
|
37
|
-
},
|
|
38
|
-
incremental: {
|
|
39
|
-
flags: '--incremental',
|
|
40
|
-
description: 'Only analyze files changed since last run',
|
|
41
|
-
default: false,
|
|
42
|
-
},
|
|
43
|
-
noConfirm: {
|
|
44
|
-
flags: '--no-confirm',
|
|
45
|
-
description: 'Skip cost confirmation prompt (required for CI)',
|
|
46
|
-
default: false,
|
|
47
|
-
},
|
|
48
|
-
dryRun: {
|
|
49
|
-
flags: '--dry-run',
|
|
50
|
-
description: 'Show cost estimate without running analysis',
|
|
51
|
-
default: false,
|
|
52
|
-
},
|
|
53
|
-
ignore: {
|
|
54
|
-
flags: '--ignore <patterns...>',
|
|
55
|
-
description: 'Additional glob patterns to ignore',
|
|
56
|
-
default: [],
|
|
57
|
-
},
|
|
58
|
-
jsonSummary: {
|
|
59
|
-
flags: '--json-summary',
|
|
60
|
-
description: 'Output machine-readable JSON summary to stdout',
|
|
61
|
-
default: false,
|
|
62
|
-
},
|
|
63
|
-
verbose: {
|
|
64
|
-
flags: '--verbose',
|
|
65
|
-
description: 'Enable verbose debug output',
|
|
66
|
-
default: false,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Apply standard flags to a commander command.
|
|
72
|
-
* @param {import('commander').Command} command
|
|
73
|
-
* @param {string[]} flagNames - Which flags to apply
|
|
74
|
-
* @returns {import('commander').Command}
|
|
75
|
-
*/
|
|
76
|
-
export function applyFlags(command, flagNames) {
|
|
77
|
-
for (const name of flagNames) {
|
|
78
|
-
const def = FLAG_DEFINITIONS[name];
|
|
79
|
-
if (!def) continue;
|
|
80
|
-
if (def.parseArg) {
|
|
81
|
-
command.option(def.flags, def.description, def.parseArg, def.default);
|
|
82
|
-
} else {
|
|
83
|
-
command.option(def.flags, def.description, def.default);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return command;
|
|
87
|
-
}
|