legacyver 2.0.0 → 2.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 +1 -1
- package/README.md +83 -128
- package/bin/legacyver.js +25 -48
- package/package.json +25 -23
- package/src/cache/hash.js +10 -9
- package/src/cache/index.js +65 -43
- package/src/cli/commands/analyze.js +190 -170
- package/src/cli/commands/cache.js +35 -15
- package/src/cli/commands/init.js +108 -32
- package/src/cli/commands/providers.js +81 -46
- package/src/cli/commands/version.js +10 -7
- package/src/cli/flags.js +87 -0
- package/src/cli/ui.js +77 -58
- package/src/crawler/filters.js +40 -32
- package/src/crawler/index.js +36 -52
- package/src/crawler/manifest.js +43 -31
- package/src/crawler/walk.js +38 -32
- package/src/llm/chunker.js +56 -34
- package/src/llm/cost-estimator.js +51 -68
- package/src/llm/index.js +43 -16
- package/src/llm/prompts.js +33 -45
- package/src/llm/providers/anthropic.js +57 -0
- package/src/llm/providers/google.js +65 -0
- package/src/llm/providers/groq.js +52 -0
- package/src/llm/providers/ollama.js +65 -38
- package/src/llm/providers/openai.js +52 -0
- package/src/llm/queue.js +94 -54
- package/src/parser/ast/generic.js +222 -45
- package/src/parser/ast/go.js +205 -86
- package/src/parser/ast/java.js +146 -76
- package/src/parser/ast/javascript.js +241 -173
- package/src/parser/ast/python.js +199 -76
- package/src/parser/ast/tree-sitter-init.js +80 -0
- package/src/parser/ast/typescript.js +244 -10
- package/src/parser/call-graph.js +67 -50
- package/src/parser/index.js +86 -61
- package/src/parser/pkg-builder.js +83 -36
- package/src/renderer/html.js +135 -63
- package/src/renderer/index.js +35 -23
- package/src/renderer/json.js +35 -17
- package/src/renderer/markdown.js +117 -83
- package/src/utils/config.js +57 -48
- package/src/utils/errors.js +41 -26
- package/src/utils/logger.js +53 -32
- package/.agent/skills/openspec-apply-change/SKILL.md +0 -156
- package/.agent/skills/openspec-archive-change/SKILL.md +0 -114
- package/.agent/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.agent/skills/openspec-continue-change/SKILL.md +0 -118
- package/.agent/skills/openspec-explore/SKILL.md +0 -290
- package/.agent/skills/openspec-ff-change/SKILL.md +0 -101
- package/.agent/skills/openspec-new-change/SKILL.md +0 -74
- package/.agent/skills/openspec-onboard/SKILL.md +0 -529
- package/.agent/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.agent/skills/openspec-verify-change/SKILL.md +0 -168
- package/.agent/workflows/opsx-apply.md +0 -149
- package/.agent/workflows/opsx-archive.md +0 -154
- package/.agent/workflows/opsx-bulk-archive.md +0 -239
- package/.agent/workflows/opsx-continue.md +0 -111
- package/.agent/workflows/opsx-explore.md +0 -171
- package/.agent/workflows/opsx-ff.md +0 -91
- package/.agent/workflows/opsx-new.md +0 -66
- package/.agent/workflows/opsx-onboard.md +0 -522
- package/.agent/workflows/opsx-sync.md +0 -131
- package/.agent/workflows/opsx-verify.md +0 -161
- package/.github/prompts/opsx-apply.prompt.md +0 -149
- package/.github/prompts/opsx-archive.prompt.md +0 -154
- package/.github/prompts/opsx-bulk-archive.prompt.md +0 -239
- package/.github/prompts/opsx-continue.prompt.md +0 -111
- package/.github/prompts/opsx-explore.prompt.md +0 -171
- package/.github/prompts/opsx-ff.prompt.md +0 -91
- package/.github/prompts/opsx-new.prompt.md +0 -66
- package/.github/prompts/opsx-onboard.prompt.md +0 -522
- package/.github/prompts/opsx-sync.prompt.md +0 -131
- package/.github/prompts/opsx-verify.prompt.md +0 -161
- package/.github/skills/openspec-apply-change/SKILL.md +0 -156
- package/.github/skills/openspec-archive-change/SKILL.md +0 -114
- package/.github/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.github/skills/openspec-continue-change/SKILL.md +0 -118
- package/.github/skills/openspec-explore/SKILL.md +0 -290
- package/.github/skills/openspec-ff-change/SKILL.md +0 -101
- package/.github/skills/openspec-new-change/SKILL.md +0 -74
- package/.github/skills/openspec-onboard/SKILL.md +0 -529
- package/.github/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.github/skills/openspec-verify-change/SKILL.md +0 -168
- package/.legacyverignore.example +0 -43
- package/.opencode/command/opsx-apply.md +0 -149
- package/.opencode/command/opsx-archive.md +0 -154
- package/.opencode/command/opsx-bulk-archive.md +0 -239
- package/.opencode/command/opsx-continue.md +0 -111
- package/.opencode/command/opsx-explore.md +0 -171
- package/.opencode/command/opsx-ff.md +0 -91
- package/.opencode/command/opsx-new.md +0 -66
- package/.opencode/command/opsx-onboard.md +0 -522
- package/.opencode/command/opsx-sync.md +0 -131
- package/.opencode/command/opsx-verify.md +0 -161
- package/.opencode/skills/openspec-apply-change/SKILL.md +0 -156
- package/.opencode/skills/openspec-archive-change/SKILL.md +0 -114
- package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.opencode/skills/openspec-continue-change/SKILL.md +0 -118
- package/.opencode/skills/openspec-explore/SKILL.md +0 -290
- package/.opencode/skills/openspec-ff-change/SKILL.md +0 -101
- package/.opencode/skills/openspec-new-change/SKILL.md +0 -74
- package/.opencode/skills/openspec-onboard/SKILL.md +0 -529
- package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.opencode/skills/openspec-verify-change/SKILL.md +0 -168
- package/nul +0 -2
- package/src/llm/free-model.js +0 -40
- package/src/llm/providers/openrouter.js +0 -67
- package/src/llm/re-prompter.js +0 -41
- package/src/llm/validator.js +0 -72
- package/src/parser/ast/laravel/blade.js +0 -56
- package/src/parser/ast/laravel/classifier.js +0 -30
- package/src/parser/ast/laravel/controller.js +0 -35
- package/src/parser/ast/laravel/index.js +0 -54
- package/src/parser/ast/laravel/model.js +0 -41
- package/src/parser/ast/laravel/provider.js +0 -28
- package/src/parser/ast/laravel/routes.js +0 -45
- package/src/parser/ast/php.js +0 -129
- package/src/parser/body-extractor.js +0 -40
- package/src/parser/complexity-scorer.js +0 -59
- package/src/parser/pattern-detector.js +0 -71
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# legacyver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generate technical documentation from undocumented or legacy codebases using AST parsing and LLMs.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
npx legacyver analyze ./src
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
No documentation written beforehand. No configuration required. One command.
|
|
10
|
-
|
|
11
|
-
---
|
|
5
|
+
Legacyver crawls your source code, extracts structural facts with AST parsers (Tree-sitter + regex fallback), sends them to an LLM with anti-hallucination constraints, and outputs clean Markdown, HTML, or JSON documentation.
|
|
12
6
|
|
|
13
7
|
## Install
|
|
14
8
|
|
|
@@ -16,183 +10,144 @@ No documentation written beforehand. No configuration required. One command.
|
|
|
16
10
|
npm install -g legacyver
|
|
17
11
|
```
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx legacyver analyze ./src
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
---
|
|
13
|
+
Requires Node.js >= 18.0.0.
|
|
26
14
|
|
|
27
15
|
## Quick Start
|
|
28
16
|
|
|
29
|
-
**1. Initialize (saves your API key):**
|
|
30
|
-
|
|
31
17
|
```bash
|
|
18
|
+
# 1. Run the setup wizard
|
|
32
19
|
legacyver init
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**2. Analyze a codebase:**
|
|
36
20
|
|
|
37
|
-
|
|
21
|
+
# 2. Generate documentation
|
|
38
22
|
legacyver analyze ./src
|
|
23
|
+
|
|
24
|
+
# 3. View output
|
|
25
|
+
open legacyver-docs/index.md
|
|
39
26
|
```
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
### Using Ollama (Free, Local)
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
legacyver-docs/
|
|
45
|
-
index.md ← project overview + dependency graph + route map (Laravel)
|
|
46
|
-
SUMMARY.md ← GitBook/Docusaurus compatible table of contents
|
|
47
|
-
src/
|
|
48
|
-
app.md
|
|
49
|
-
routes/users.md
|
|
50
|
-
...
|
|
51
|
-
```
|
|
30
|
+
No API key required — just have [Ollama](https://ollama.ai) running:
|
|
52
31
|
|
|
53
|
-
|
|
32
|
+
```bash
|
|
33
|
+
legacyver analyze ./src --provider ollama
|
|
34
|
+
```
|
|
54
35
|
|
|
55
|
-
## CLI
|
|
36
|
+
## CLI Reference
|
|
56
37
|
|
|
57
|
-
### `legacyver analyze <
|
|
38
|
+
### `legacyver analyze <path>`
|
|
58
39
|
|
|
59
|
-
|
|
40
|
+
Main command. Analyzes a codebase and generates documentation.
|
|
60
41
|
|
|
61
|
-
| Flag |
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
| `--
|
|
69
|
-
| `--
|
|
70
|
-
| `--
|
|
71
|
-
| `--
|
|
72
|
-
| `--
|
|
42
|
+
| Flag | Description | Default |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `-p, --provider <name>` | LLM provider (`anthropic`, `openai`, `google`, `groq`, `ollama`) | `anthropic` |
|
|
45
|
+
| `-m, --model <name>` | Model name (overrides provider default) | Provider default |
|
|
46
|
+
| `-o, --output <dir>` | Output directory | `./legacyver-docs` |
|
|
47
|
+
| `-f, --format <type>` | Output format: `markdown`, `html`, `json` | `markdown` |
|
|
48
|
+
| `-c, --concurrency <n>` | Concurrent LLM requests | `5` |
|
|
49
|
+
| `--max-file-size <kb>` | Skip files larger than this (KB) | `500` |
|
|
50
|
+
| `--incremental` | Only re-analyze changed files | `false` |
|
|
51
|
+
| `--no-confirm` | Skip cost confirmation prompt | `false` |
|
|
52
|
+
| `--dry-run` | Show cost estimate, no LLM calls | `false` |
|
|
53
|
+
| `--ignore <patterns...>` | Additional glob patterns to ignore | `[]` |
|
|
54
|
+
| `--json-summary` | Output JSON summary to stdout | `false` |
|
|
55
|
+
| `--verbose` | Enable debug output | `false` |
|
|
73
56
|
|
|
74
57
|
### `legacyver init`
|
|
75
58
|
|
|
76
|
-
Interactive wizard.
|
|
59
|
+
Interactive setup wizard. Configures your preferred LLM provider, saves API keys to OS user config, and creates a `.legacyverrc` file.
|
|
77
60
|
|
|
78
61
|
### `legacyver providers`
|
|
79
62
|
|
|
80
|
-
|
|
63
|
+
Lists all supported providers with API key status and pricing.
|
|
81
64
|
|
|
82
65
|
### `legacyver cache clear`
|
|
83
66
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
### `legacyver --version`
|
|
67
|
+
Deletes the `.legacyver-cache/` directory.
|
|
87
68
|
|
|
88
|
-
|
|
69
|
+
## Configuration
|
|
89
70
|
|
|
90
|
-
|
|
71
|
+
Legacyver uses [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) for configuration. It searches for:
|
|
91
72
|
|
|
92
|
-
|
|
73
|
+
- `.legacyverrc` (JSON)
|
|
74
|
+
- `.legacyverrc.json`
|
|
75
|
+
- `.legacyverrc.yaml`
|
|
76
|
+
- `legacyver.config.js`
|
|
77
|
+
- `legacyver.config.mjs`
|
|
93
78
|
|
|
94
|
-
|
|
79
|
+
### `.legacyverrc` Schema
|
|
95
80
|
|
|
96
81
|
```json
|
|
97
82
|
{
|
|
98
|
-
"provider": "
|
|
99
|
-
"model":
|
|
83
|
+
"provider": "anthropic",
|
|
84
|
+
"model": null,
|
|
100
85
|
"format": "markdown",
|
|
101
|
-
"
|
|
102
|
-
"concurrency":
|
|
86
|
+
"output": "./legacyver-docs",
|
|
87
|
+
"concurrency": 5,
|
|
103
88
|
"maxFileSizeKb": 500,
|
|
104
|
-
"incremental":
|
|
89
|
+
"incremental": false,
|
|
90
|
+
"ignore": ["test/**", "**/*.test.*"],
|
|
91
|
+
"languages": ["javascript", "typescript", "python", "java", "go"]
|
|
105
92
|
}
|
|
106
93
|
```
|
|
107
94
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Also supported: `legacyver.config.js`, `legacyver.config.yaml`.
|
|
111
|
-
|
|
112
|
-
---
|
|
95
|
+
CLI flags always take precedence over config file values.
|
|
113
96
|
|
|
114
97
|
## Supported Languages
|
|
115
98
|
|
|
116
|
-
| Language | Extensions |
|
|
117
|
-
|
|
118
|
-
| JavaScript | `.js`, `.jsx`, `.mjs` |
|
|
119
|
-
| TypeScript | `.ts`, `.tsx` |
|
|
120
|
-
| Python | `.py` |
|
|
121
|
-
| Java | `.java` |
|
|
122
|
-
| Go | `.go` |
|
|
123
|
-
| PHP | `.php`, `.blade.php` | Laravel (auto-detected) |
|
|
124
|
-
|
|
125
|
-
**Laravel extras:** When an `artisan` file is detected, Legacyver automatically extracts:
|
|
126
|
-
- Route Map (Method, URI, Controller, Middleware, Route Name)
|
|
127
|
-
- Model Relationships (as Mermaid `erDiagram`)
|
|
128
|
-
- Service Provider Bindings
|
|
99
|
+
| Language | Extensions | Parser |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| JavaScript | `.js`, `.jsx`, `.mjs` | Tree-sitter (WASM) or regex fallback |
|
|
102
|
+
| TypeScript | `.ts`, `.tsx` | Tree-sitter (WASM) or regex fallback |
|
|
103
|
+
| Python | `.py` | Tree-sitter (WASM) or regex fallback |
|
|
104
|
+
| Java | `.java` | Tree-sitter (WASM) or regex fallback |
|
|
105
|
+
| Go | `.go` | Tree-sitter (WASM) or regex fallback |
|
|
129
106
|
|
|
130
|
-
|
|
107
|
+
When Tree-sitter WASM grammars are not available, legacyver automatically falls back to a generic regex-based parser that extracts function signatures, class definitions, and imports.
|
|
131
108
|
|
|
132
109
|
## Supported LLM Providers
|
|
133
110
|
|
|
134
|
-
| Provider |
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
|
111
|
+
| Provider | Default Model | Input Cost/1k tokens | Output Cost/1k tokens | API Key Env Var |
|
|
112
|
+
|---|---|---|---|---|
|
|
113
|
+
| Anthropic | `claude-haiku-3-5` | $0.00025 | $0.00125 | `ANTHROPIC_API_KEY` |
|
|
114
|
+
| OpenAI | `gpt-4o-mini` | $0.00015 | $0.00060 | `OPENAI_API_KEY` |
|
|
115
|
+
| Google | `gemini-1.5-flash` | $0.000075 | $0.00030 | `GOOGLE_API_KEY` |
|
|
116
|
+
| Groq | `llama-3.1-70b-versatile` | $0.00059 | $0.00079 | `GROQ_API_KEY` |
|
|
117
|
+
| Ollama | `llama3.2` | Free | Free | None (local) |
|
|
138
118
|
|
|
139
|
-
|
|
119
|
+
## Ignore Rules
|
|
140
120
|
|
|
141
|
-
|
|
121
|
+
Legacyver respects a `.legacyverignore` file (same syntax as `.gitignore`) in your project root.
|
|
142
122
|
|
|
143
|
-
|
|
123
|
+
Default ignores include: `node_modules`, `.git`, `dist`, `build`, `__pycache__`, `venv`, `vendor`, minified files, lock files, and more.
|
|
144
124
|
|
|
145
|
-
|
|
125
|
+
## CI/CD Integration
|
|
146
126
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
coverage/
|
|
152
|
-
*.min.js
|
|
127
|
+
Legacyver auto-detects non-interactive environments (no TTY) and disables spinners/prompts. For CI pipelines:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
legacyver analyze ./src --provider openai --no-confirm --json-summary
|
|
153
131
|
```
|
|
154
132
|
|
|
155
|
-
|
|
133
|
+
The `--json-summary` flag outputs a machine-readable summary to stdout. The `--no-confirm` flag is required in non-interactive mode (otherwise legacyver exits with code 4).
|
|
156
134
|
|
|
157
|
-
|
|
135
|
+
### Incremental Builds
|
|
158
136
|
|
|
159
|
-
|
|
137
|
+
Use `--incremental` to skip unchanged files between CI runs:
|
|
160
138
|
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
- name: Generate docs
|
|
164
|
-
env:
|
|
165
|
-
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
166
|
-
run: |
|
|
167
|
-
npx legacyver analyze ./src --no-confirm --incremental --out ./docs
|
|
139
|
+
```bash
|
|
140
|
+
legacyver analyze ./src --incremental --no-confirm
|
|
168
141
|
```
|
|
169
142
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
---
|
|
143
|
+
Cache is stored in `.legacyver-cache/` (auto-added to `.gitignore`).
|
|
173
144
|
|
|
174
145
|
## How It Works
|
|
175
146
|
|
|
176
|
-
1. **
|
|
177
|
-
2. **
|
|
178
|
-
3. **
|
|
179
|
-
4. **
|
|
180
|
-
5. **Validate** — Post-generation checks: hallucination detection, completeness check (all exports documented)
|
|
181
|
-
6. **Render** — Writes Markdown/HTML/JSON to output directory
|
|
182
|
-
|
|
183
|
-
**Design principle:** The LLM is a _writer_, not an analyst. AST extracts facts; LLM only explains them.
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## Contributing
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
git clone https://github.com/user/legacyver
|
|
191
|
-
npm install
|
|
192
|
-
npx vitest run
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
---
|
|
147
|
+
1. **Crawler** discovers source files using fast-glob, respecting ignore rules
|
|
148
|
+
2. **AST Parser** extracts structural facts (functions, classes, imports, exports, call graph) using Tree-sitter or regex fallback
|
|
149
|
+
3. **LLM Engine** sends facts + source code to the LLM with anti-hallucination constraints — the LLM can only describe what's actually in the code
|
|
150
|
+
4. **Renderer** assembles the output as Markdown (with Mermaid diagrams), self-contained HTML (with search), or structured JSON
|
|
196
151
|
|
|
197
152
|
## License
|
|
198
153
|
|
package/bin/legacyver.js
CHANGED
|
@@ -1,56 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { registerAnalyzeCommand } from '../src/cli/commands/analyze.js';
|
|
6
|
+
import { registerInitCommand } from '../src/cli/commands/init.js';
|
|
7
|
+
import { registerProvidersCommand } from '../src/cli/commands/providers.js';
|
|
8
|
+
import { registerCacheCommand } from '../src/cli/commands/cache.js';
|
|
9
|
+
import { setVerbose } from '../src/utils/logger.js';
|
|
7
10
|
|
|
8
|
-
const
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const pkg = require('../package.json');
|
|
9
13
|
|
|
10
|
-
program
|
|
11
|
-
.name('legacyver')
|
|
12
|
-
.description('AI-powered CLI tool to auto-generate technical documentation from legacy/undocumented codebases')
|
|
13
|
-
.version(pkg.version, '-v, --version', 'Output the current version')
|
|
14
|
-
.option('--verbose', 'Enable verbose debug logging');
|
|
14
|
+
const program = new Command();
|
|
15
15
|
|
|
16
|
-
// analyze command
|
|
17
|
-
const analyzeCmd = require('../src/cli/commands/analyze');
|
|
18
16
|
program
|
|
19
|
-
.
|
|
20
|
-
.description('
|
|
21
|
-
.
|
|
22
|
-
.option('--
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const initCmd = require('../src/cli/commands/init');
|
|
35
|
-
program
|
|
36
|
-
.command('init')
|
|
37
|
-
.description('Interactive setup wizard — saves API key and creates .legacyverrc')
|
|
38
|
-
.action(initCmd);
|
|
39
|
-
|
|
40
|
-
// providers command
|
|
41
|
-
const providersCmd = require('../src/cli/commands/providers');
|
|
42
|
-
program
|
|
43
|
-
.command('providers')
|
|
44
|
-
.description('List supported LLM providers and available models')
|
|
45
|
-
.action(providersCmd);
|
|
46
|
-
|
|
47
|
-
// cache command
|
|
48
|
-
const cacheCmd = require('../src/cli/commands/cache');
|
|
49
|
-
program
|
|
50
|
-
.command('cache')
|
|
51
|
-
.description('Manage the incremental analysis cache')
|
|
52
|
-
.command('clear')
|
|
53
|
-
.description('Delete the .legacyver-cache/ directory')
|
|
54
|
-
.action(cacheCmd);
|
|
17
|
+
.name('legacyver')
|
|
18
|
+
.description('Generate technical documentation from undocumented or legacy codebases')
|
|
19
|
+
.version(pkg.version)
|
|
20
|
+
.option('--verbose', 'Enable verbose debug output', false)
|
|
21
|
+
.hook('preAction', (thisCommand) => {
|
|
22
|
+
const opts = thisCommand.opts();
|
|
23
|
+
if (opts.verbose) {
|
|
24
|
+
setVerbose(true);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
registerAnalyzeCommand(program);
|
|
29
|
+
registerInitCommand(program);
|
|
30
|
+
registerProvidersCommand(program);
|
|
31
|
+
registerCacheCommand(program);
|
|
55
32
|
|
|
56
33
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "legacyver",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Generate technical documentation from undocumented or legacy codebases using AST parsing and LLMs",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"legacyver": "bin/legacyver.js"
|
|
8
8
|
},
|
|
9
|
-
"type": "commonjs",
|
|
10
9
|
"engines": {
|
|
11
10
|
"node": ">=18.0.0"
|
|
12
11
|
},
|
|
13
12
|
"scripts": {
|
|
14
13
|
"test": "vitest run",
|
|
15
14
|
"test:watch": "vitest",
|
|
16
|
-
"lint": "eslint src/
|
|
15
|
+
"lint": "eslint src/ test/"
|
|
17
16
|
},
|
|
18
17
|
"keywords": [
|
|
19
18
|
"documentation",
|
|
20
|
-
"
|
|
19
|
+
"legacy",
|
|
20
|
+
"codebase",
|
|
21
21
|
"ast",
|
|
22
22
|
"llm",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"tree-sitter"
|
|
23
|
+
"tree-sitter",
|
|
24
|
+
"cli"
|
|
26
25
|
],
|
|
27
|
-
"author": "",
|
|
28
26
|
"license": "MIT",
|
|
27
|
+
"type": "module",
|
|
29
28
|
"dependencies": {
|
|
30
|
-
"
|
|
29
|
+
"@anthropic-ai/sdk": "^0.75.0",
|
|
30
|
+
"@google/generative-ai": "^0.24.1",
|
|
31
|
+
"chalk": "^5.6.2",
|
|
31
32
|
"cli-progress": "^3.12.0",
|
|
32
|
-
"commander": "^
|
|
33
|
-
"conf": "^
|
|
33
|
+
"commander": "^14.0.3",
|
|
34
|
+
"conf": "^15.1.0",
|
|
34
35
|
"cosmiconfig": "^9.0.0",
|
|
35
|
-
"fast-glob": "^3.3.
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
36
|
+
"fast-glob": "^3.3.3",
|
|
37
|
+
"groq-sdk": "^0.37.0",
|
|
38
|
+
"ignore": "^7.0.5",
|
|
39
|
+
"marked": "^17.0.3",
|
|
40
|
+
"openai": "^6.22.0",
|
|
41
|
+
"ora": "^9.3.0",
|
|
42
|
+
"p-limit": "^7.3.0",
|
|
43
|
+
"p-retry": "^7.1.1",
|
|
44
|
+
"picocolors": "^1.1.1",
|
|
45
|
+
"web-tree-sitter": "^0.26.5"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
|
-
"eslint": "^
|
|
47
|
-
"vitest": "^
|
|
48
|
+
"eslint": "^10.0.0",
|
|
49
|
+
"vitest": "^4.0.18"
|
|
48
50
|
}
|
|
49
51
|
}
|
package/src/cache/hash.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* File hash computation for incremental cache.
|
|
3
|
+
* Uses Node.js crypto SHA-256 to produce deterministic file fingerprints.
|
|
4
|
+
*/
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
|
-
* Compute SHA-256 hash of a file.
|
|
8
|
-
* @param {string} filePath
|
|
9
|
-
* @returns {string}
|
|
10
|
+
* Compute SHA-256 hash of a file's contents.
|
|
11
|
+
* @param {string} filePath - Absolute path to file
|
|
12
|
+
* @returns {string} Hex-encoded SHA-256 hash with "sha256:" prefix
|
|
10
13
|
*/
|
|
11
|
-
function computeHash(filePath) {
|
|
14
|
+
export function computeHash(filePath) {
|
|
12
15
|
const content = readFileSync(filePath);
|
|
13
16
|
return 'sha256:' + createHash('sha256').update(content).digest('hex');
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
module.exports = { computeHash };
|
package/src/cache/index.js
CHANGED
|
@@ -1,47 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Incremental cache module.
|
|
3
|
+
* Persists file hashes to .legacyver-cache/hashes.json so that
|
|
4
|
+
* unchanged files can skip LLM re-analysis on subsequent runs.
|
|
5
|
+
*/
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const logger = require('../utils/logger');
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
6
9
|
|
|
7
10
|
const CACHE_FILE = 'hashes.json';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
|
-
* Load cache from .
|
|
11
|
-
* @param {string} cacheDir
|
|
12
|
-
* @returns {
|
|
13
|
+
* Load the cache map from disk.
|
|
14
|
+
* @param {string} cacheDir - Path to .legacyver-cache directory
|
|
15
|
+
* @returns {Record<string, { hash: string, generatedAt: string }>} Cache map (empty object if no cache)
|
|
13
16
|
*/
|
|
14
|
-
function loadCache(cacheDir) {
|
|
15
|
-
const cachePath =
|
|
16
|
-
if (!existsSync(cachePath))
|
|
17
|
+
export function loadCache(cacheDir) {
|
|
18
|
+
const cachePath = join(cacheDir, CACHE_FILE);
|
|
19
|
+
if (!existsSync(cachePath)) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
17
22
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
const raw = readFileSync(cachePath, 'utf-8');
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
// Corrupted cache — start fresh
|
|
21
27
|
return {};
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
/**
|
|
26
|
-
* Save cache to .
|
|
27
|
-
*
|
|
28
|
-
* @param {
|
|
32
|
+
* Save the cache map to disk.
|
|
33
|
+
* Creates the cache directory if it doesn't exist.
|
|
34
|
+
* @param {string} cacheDir - Path to .legacyver-cache directory
|
|
35
|
+
* @param {Record<string, { hash: string, generatedAt: string }>} cacheMap - Cache map to persist
|
|
29
36
|
*/
|
|
30
|
-
function saveCache(cacheDir,
|
|
37
|
+
export function saveCache(cacheDir, cacheMap) {
|
|
31
38
|
mkdirSync(cacheDir, { recursive: true });
|
|
32
|
-
const cachePath =
|
|
33
|
-
writeFileSync(cachePath, JSON.stringify(
|
|
39
|
+
const cachePath = join(cacheDir, CACHE_FILE);
|
|
40
|
+
writeFileSync(cachePath, JSON.stringify(cacheMap, null, 2), 'utf-8');
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
37
|
-
* Separate
|
|
38
|
-
*
|
|
39
|
-
* @param {
|
|
40
|
-
* @
|
|
44
|
+
* Separate manifest entries into cache hits and misses.
|
|
45
|
+
* A hit means the file hash matches the cached hash — no re-analysis needed.
|
|
46
|
+
* @param {import('../crawler/manifest.js').FileManifest[]} manifest - Current file manifest
|
|
47
|
+
* @param {Record<string, { hash: string }>} cacheMap - Loaded cache map
|
|
48
|
+
* @returns {{ hits: import('../crawler/manifest.js').FileManifest[], misses: import('../crawler/manifest.js').FileManifest[] }}
|
|
41
49
|
*/
|
|
42
|
-
function getCacheHits(manifest, cacheMap) {
|
|
50
|
+
export function getCacheHits(manifest, cacheMap) {
|
|
43
51
|
const hits = [];
|
|
44
52
|
const misses = [];
|
|
53
|
+
|
|
45
54
|
for (const file of manifest) {
|
|
46
55
|
const cached = cacheMap[file.relativePath];
|
|
47
56
|
if (cached && cached.hash === file.hash) {
|
|
@@ -50,35 +59,48 @@ function getCacheHits(manifest, cacheMap) {
|
|
|
50
59
|
misses.push(file);
|
|
51
60
|
}
|
|
52
61
|
}
|
|
62
|
+
|
|
53
63
|
return { hits, misses };
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
/**
|
|
57
|
-
* Remove entries for files that no longer exist on disk.
|
|
58
|
-
* @param {
|
|
59
|
-
* @param {string[]} currentPaths
|
|
67
|
+
* Remove cache entries for files that no longer exist on disk.
|
|
68
|
+
* @param {Record<string, { hash: string }>} cacheMap - Current cache map
|
|
69
|
+
* @param {string[]} currentPaths - Relative paths of files currently on disk
|
|
70
|
+
* @returns {Record<string, { hash: string }>} Cleaned cache map
|
|
60
71
|
*/
|
|
61
|
-
function purgeDeleted(cacheMap, currentPaths) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
export function purgeDeleted(cacheMap, currentPaths) {
|
|
73
|
+
const pathSet = new Set(currentPaths);
|
|
74
|
+
const cleaned = {};
|
|
75
|
+
for (const [key, value] of Object.entries(cacheMap)) {
|
|
76
|
+
if (pathSet.has(key)) {
|
|
77
|
+
cleaned[key] = value;
|
|
66
78
|
}
|
|
67
79
|
}
|
|
80
|
+
return cleaned;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
/**
|
|
71
|
-
* Auto-add .legacyver-cache/ to .gitignore if
|
|
72
|
-
*
|
|
84
|
+
* Auto-add .legacyver-cache/ to .gitignore if .gitignore exists
|
|
85
|
+
* and doesn't already contain the entry.
|
|
86
|
+
* @param {string} projectRoot - Root directory of the project being analyzed
|
|
73
87
|
*/
|
|
74
|
-
function
|
|
75
|
-
const gitignorePath =
|
|
76
|
-
if (!existsSync(gitignorePath))
|
|
77
|
-
|
|
78
|
-
if (!content.includes('.legacyver-cache')) {
|
|
79
|
-
appendFileSync(gitignorePath, '\n.legacyver-cache/\n');
|
|
80
|
-
logger.info('Added .legacyver-cache/ to .gitignore');
|
|
88
|
+
export function addToGitignore(projectRoot) {
|
|
89
|
+
const gitignorePath = join(projectRoot, '.gitignore');
|
|
90
|
+
if (!existsSync(gitignorePath)) {
|
|
91
|
+
return;
|
|
81
92
|
}
|
|
82
|
-
}
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
const content = readFileSync(gitignorePath, 'utf-8');
|
|
95
|
+
const entry = '.legacyver-cache/';
|
|
96
|
+
|
|
97
|
+
// Check if already present (exact line match)
|
|
98
|
+
const lines = content.split('\n');
|
|
99
|
+
if (lines.some((line) => line.trim() === entry || line.trim() === '.legacyver-cache')) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Append entry with a newline separator
|
|
104
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
105
|
+
writeFileSync(gitignorePath, content + separator + entry + '\n', 'utf-8');
|
|
106
|
+
}
|