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.
Files changed (121) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +83 -128
  3. package/bin/legacyver.js +25 -48
  4. package/package.json +25 -23
  5. package/src/cache/hash.js +10 -9
  6. package/src/cache/index.js +65 -43
  7. package/src/cli/commands/analyze.js +190 -170
  8. package/src/cli/commands/cache.js +35 -15
  9. package/src/cli/commands/init.js +108 -32
  10. package/src/cli/commands/providers.js +81 -46
  11. package/src/cli/commands/version.js +10 -7
  12. package/src/cli/flags.js +87 -0
  13. package/src/cli/ui.js +77 -58
  14. package/src/crawler/filters.js +40 -32
  15. package/src/crawler/index.js +36 -52
  16. package/src/crawler/manifest.js +43 -31
  17. package/src/crawler/walk.js +38 -32
  18. package/src/llm/chunker.js +56 -34
  19. package/src/llm/cost-estimator.js +51 -68
  20. package/src/llm/index.js +43 -16
  21. package/src/llm/prompts.js +33 -45
  22. package/src/llm/providers/anthropic.js +57 -0
  23. package/src/llm/providers/google.js +65 -0
  24. package/src/llm/providers/groq.js +52 -0
  25. package/src/llm/providers/ollama.js +65 -38
  26. package/src/llm/providers/openai.js +52 -0
  27. package/src/llm/queue.js +94 -54
  28. package/src/parser/ast/generic.js +222 -45
  29. package/src/parser/ast/go.js +205 -86
  30. package/src/parser/ast/java.js +146 -76
  31. package/src/parser/ast/javascript.js +241 -173
  32. package/src/parser/ast/python.js +199 -76
  33. package/src/parser/ast/tree-sitter-init.js +80 -0
  34. package/src/parser/ast/typescript.js +244 -10
  35. package/src/parser/call-graph.js +67 -50
  36. package/src/parser/index.js +86 -61
  37. package/src/parser/pkg-builder.js +83 -36
  38. package/src/renderer/html.js +135 -63
  39. package/src/renderer/index.js +35 -23
  40. package/src/renderer/json.js +35 -17
  41. package/src/renderer/markdown.js +117 -83
  42. package/src/utils/config.js +57 -48
  43. package/src/utils/errors.js +41 -26
  44. package/src/utils/logger.js +53 -32
  45. package/.agent/skills/openspec-apply-change/SKILL.md +0 -156
  46. package/.agent/skills/openspec-archive-change/SKILL.md +0 -114
  47. package/.agent/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  48. package/.agent/skills/openspec-continue-change/SKILL.md +0 -118
  49. package/.agent/skills/openspec-explore/SKILL.md +0 -290
  50. package/.agent/skills/openspec-ff-change/SKILL.md +0 -101
  51. package/.agent/skills/openspec-new-change/SKILL.md +0 -74
  52. package/.agent/skills/openspec-onboard/SKILL.md +0 -529
  53. package/.agent/skills/openspec-sync-specs/SKILL.md +0 -138
  54. package/.agent/skills/openspec-verify-change/SKILL.md +0 -168
  55. package/.agent/workflows/opsx-apply.md +0 -149
  56. package/.agent/workflows/opsx-archive.md +0 -154
  57. package/.agent/workflows/opsx-bulk-archive.md +0 -239
  58. package/.agent/workflows/opsx-continue.md +0 -111
  59. package/.agent/workflows/opsx-explore.md +0 -171
  60. package/.agent/workflows/opsx-ff.md +0 -91
  61. package/.agent/workflows/opsx-new.md +0 -66
  62. package/.agent/workflows/opsx-onboard.md +0 -522
  63. package/.agent/workflows/opsx-sync.md +0 -131
  64. package/.agent/workflows/opsx-verify.md +0 -161
  65. package/.github/prompts/opsx-apply.prompt.md +0 -149
  66. package/.github/prompts/opsx-archive.prompt.md +0 -154
  67. package/.github/prompts/opsx-bulk-archive.prompt.md +0 -239
  68. package/.github/prompts/opsx-continue.prompt.md +0 -111
  69. package/.github/prompts/opsx-explore.prompt.md +0 -171
  70. package/.github/prompts/opsx-ff.prompt.md +0 -91
  71. package/.github/prompts/opsx-new.prompt.md +0 -66
  72. package/.github/prompts/opsx-onboard.prompt.md +0 -522
  73. package/.github/prompts/opsx-sync.prompt.md +0 -131
  74. package/.github/prompts/opsx-verify.prompt.md +0 -161
  75. package/.github/skills/openspec-apply-change/SKILL.md +0 -156
  76. package/.github/skills/openspec-archive-change/SKILL.md +0 -114
  77. package/.github/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  78. package/.github/skills/openspec-continue-change/SKILL.md +0 -118
  79. package/.github/skills/openspec-explore/SKILL.md +0 -290
  80. package/.github/skills/openspec-ff-change/SKILL.md +0 -101
  81. package/.github/skills/openspec-new-change/SKILL.md +0 -74
  82. package/.github/skills/openspec-onboard/SKILL.md +0 -529
  83. package/.github/skills/openspec-sync-specs/SKILL.md +0 -138
  84. package/.github/skills/openspec-verify-change/SKILL.md +0 -168
  85. package/.legacyverignore.example +0 -43
  86. package/.opencode/command/opsx-apply.md +0 -149
  87. package/.opencode/command/opsx-archive.md +0 -154
  88. package/.opencode/command/opsx-bulk-archive.md +0 -239
  89. package/.opencode/command/opsx-continue.md +0 -111
  90. package/.opencode/command/opsx-explore.md +0 -171
  91. package/.opencode/command/opsx-ff.md +0 -91
  92. package/.opencode/command/opsx-new.md +0 -66
  93. package/.opencode/command/opsx-onboard.md +0 -522
  94. package/.opencode/command/opsx-sync.md +0 -131
  95. package/.opencode/command/opsx-verify.md +0 -161
  96. package/.opencode/skills/openspec-apply-change/SKILL.md +0 -156
  97. package/.opencode/skills/openspec-archive-change/SKILL.md +0 -114
  98. package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  99. package/.opencode/skills/openspec-continue-change/SKILL.md +0 -118
  100. package/.opencode/skills/openspec-explore/SKILL.md +0 -290
  101. package/.opencode/skills/openspec-ff-change/SKILL.md +0 -101
  102. package/.opencode/skills/openspec-new-change/SKILL.md +0 -74
  103. package/.opencode/skills/openspec-onboard/SKILL.md +0 -529
  104. package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -138
  105. package/.opencode/skills/openspec-verify-change/SKILL.md +0 -168
  106. package/nul +0 -2
  107. package/src/llm/free-model.js +0 -40
  108. package/src/llm/providers/openrouter.js +0 -67
  109. package/src/llm/re-prompter.js +0 -41
  110. package/src/llm/validator.js +0 -72
  111. package/src/parser/ast/laravel/blade.js +0 -56
  112. package/src/parser/ast/laravel/classifier.js +0 -30
  113. package/src/parser/ast/laravel/controller.js +0 -35
  114. package/src/parser/ast/laravel/index.js +0 -54
  115. package/src/parser/ast/laravel/model.js +0 -41
  116. package/src/parser/ast/laravel/provider.js +0 -28
  117. package/src/parser/ast/laravel/routes.js +0 -45
  118. package/src/parser/ast/php.js +0 -129
  119. package/src/parser/body-extractor.js +0 -40
  120. package/src/parser/complexity-scorer.js +0 -59
  121. package/src/parser/pattern-detector.js +0 -71
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Legacyver Contributors
3
+ Copyright (c) 2025 legacyver contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,14 +1,8 @@
1
- # Legacyver
1
+ # legacyver
2
2
 
3
- Auto-generate technical documentation for legacy and undocumented codebases using AST parsing + LLMs.
3
+ Generate technical documentation from undocumented or legacy codebases using AST parsing and LLMs.
4
4
 
5
- ```bash
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
- Or use without installing:
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
- ```bash
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
- **3. View the output:**
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 Commands
36
+ ## CLI Reference
56
37
 
57
- ### `legacyver analyze <dir>`
38
+ ### `legacyver analyze <path>`
58
39
 
59
- Run the full documentation pipeline.
40
+ Main command. Analyzes a codebase and generates documentation.
60
41
 
61
- | Flag | Default | Description |
62
- |------|---------|-------------|
63
- | `--out <dir>` | `./legacyver-docs` | Output directory |
64
- | `--format <fmt>` | `markdown` | Output format: `markdown`, `html`, `json` |
65
- | `--provider <p>` | `openrouter` | LLM provider: `openrouter`, `ollama` |
66
- | `--model <id>` | `meta-llama/llama-3.3-70b-instruct:free` | Model ID |
67
- | `--incremental` | `false` | Skip files unchanged since last run |
68
- | `--dry-run` | `false` | Estimate cost without calling LLM |
69
- | `--concurrency <n>` | `3` | Max parallel LLM requests |
70
- | `--max-file-size <kb>` | `500` | Skip files larger than this |
71
- | `--no-confirm` | | Skip cost confirmation prompt |
72
- | `--verbose` | `false` | Enable debug logging |
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. Detects existing `.legacyverrc` and warns before overwriting. Saves API key to OS user config.
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
- List all supported providers, API key status, and per-model pricing.
63
+ Lists all supported providers with API key status and pricing.
81
64
 
82
65
  ### `legacyver cache clear`
83
66
 
84
- Delete the `.legacyver-cache/` directory to force full re-analysis on next run.
85
-
86
- ### `legacyver --version`
67
+ Deletes the `.legacyver-cache/` directory.
87
68
 
88
- Print the installed version.
69
+ ## Configuration
89
70
 
90
- ---
71
+ Legacyver uses [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) for configuration. It searches for:
91
72
 
92
- ## `.legacyverrc` Schema
73
+ - `.legacyverrc` (JSON)
74
+ - `.legacyverrc.json`
75
+ - `.legacyverrc.yaml`
76
+ - `legacyver.config.js`
77
+ - `legacyver.config.mjs`
93
78
 
94
- Create a `.legacyverrc` (JSON or YAML) in your project root:
79
+ ### `.legacyverrc` Schema
95
80
 
96
81
  ```json
97
82
  {
98
- "provider": "openrouter",
99
- "model": "meta-llama/llama-3.3-70b-instruct:free",
83
+ "provider": "anthropic",
84
+ "model": null,
100
85
  "format": "markdown",
101
- "out": "./legacyver-docs",
102
- "concurrency": 3,
86
+ "output": "./legacyver-docs",
87
+ "concurrency": 5,
103
88
  "maxFileSizeKb": 500,
104
- "incremental": true
89
+ "incremental": false,
90
+ "ignore": ["test/**", "**/*.test.*"],
91
+ "languages": ["javascript", "typescript", "python", "java", "go"]
105
92
  }
106
93
  ```
107
94
 
108
- All fields are optional. CLI flags override file config.
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 | Framework Support |
117
- |----------|-----------|-------------------|
118
- | JavaScript | `.js`, `.jsx`, `.mjs` | Express (auto-detected) |
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 | Env Var | Notes |
135
- |----------|---------|-------|
136
- | OpenRouter | `OPENROUTER_API_KEY` | Default. Free models available (`:free` suffix). Get a key at [openrouter.ai/keys](https://openrouter.ai/keys) |
137
- | Ollama | | Local/offline. Requires Ollama running: `ollama serve` |
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
- **Free usage:** The default model `meta-llama/llama-3.3-70b-instruct:free` is free via OpenRouter (rate-limited). Legacyver automatically caps concurrency to 1 for free models.
119
+ ## Ignore Rules
140
120
 
141
- ---
121
+ Legacyver respects a `.legacyverignore` file (same syntax as `.gitignore`) in your project root.
142
122
 
143
- ## Ignore Files
123
+ Default ignores include: `node_modules`, `.git`, `dist`, `build`, `__pycache__`, `venv`, `vendor`, minified files, lock files, and more.
144
124
 
145
- Create a `.legacyverignore` in your project root using gitignore syntax:
125
+ ## CI/CD Integration
146
126
 
147
- ```
148
- # .legacyverignore
149
- dist/
150
- build/
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
- See `.legacyverignore.example` for a documented example.
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
- ## CI/CD Integration
137
+ Use `--incremental` to skip unchanged files between CI runs:
160
138
 
161
- ```yaml
162
- # GitHub Actions example
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
- Legacyver detects non-TTY environments and disables spinners/progress bars automatically (plain log output only).
171
-
172
- ---
143
+ Cache is stored in `.legacyver-cache/` (auto-added to `.gitignore`).
173
144
 
174
145
  ## How It Works
175
146
 
176
- 1. **Crawl** `fast-glob` walks the directory, respects `.legacyverignore`
177
- 2. **Parse** Regex/heuristic AST extracts facts: function names, params, complexity scores, imports, exports, call patterns
178
- 3. **PKG** — Assembles a Project Knowledge Graph linking all facts
179
- 4. **LLM** Sends grounded facts + raw source to LLM; system prompt enforces anti-hallucination contract
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
- const { program } = require('commander');
5
- const { readFileSync } = require('fs');
6
- const { join } = require('path');
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 pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
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
- .command('analyze <target>')
20
- .description('Analyze a directory and generate documentation')
21
- .option('--out <dir>', 'Output directory', './legacyver-docs')
22
- .option('--format <fmt>', 'Output format: markdown | html | json', 'markdown')
23
- .option('--model <model>', 'LLM model to use')
24
- .option('--provider <provider>', 'LLM provider: openrouter | ollama', 'openrouter')
25
- .option('--concurrency <n>', 'Concurrent LLM requests (1-10)', '3')
26
- .option('--dry-run', 'Run AST parsing only, no LLM calls')
27
- .option('--incremental', 'Only re-analyze changed files')
28
- .option('--no-confirm', 'Skip cost confirmation prompt')
29
- .option('--json-summary', 'Output machine-readable JSON summary')
30
- .option('--max-file-size <kb>', 'Skip files larger than this size in KB', '500')
31
- .action(analyzeCmd);
32
-
33
- // init command
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.0.0",
4
- "description": "AI-powered CLI tool to auto-generate technical documentation from legacy/undocumented codebases",
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/ bin/"
15
+ "lint": "eslint src/ test/"
17
16
  },
18
17
  "keywords": [
19
18
  "documentation",
20
- "cli",
19
+ "legacy",
20
+ "codebase",
21
21
  "ast",
22
22
  "llm",
23
- "legacy",
24
- "code-analysis",
25
- "tree-sitter"
23
+ "tree-sitter",
24
+ "cli"
26
25
  ],
27
- "author": "",
28
26
  "license": "MIT",
27
+ "type": "module",
29
28
  "dependencies": {
30
- "chalk": "^5.3.0",
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": "^11.1.0",
33
- "conf": "^12.0.0",
33
+ "commander": "^14.0.3",
34
+ "conf": "^15.1.0",
34
35
  "cosmiconfig": "^9.0.0",
35
- "fast-glob": "^3.3.2",
36
- "ignore": "^5.3.1",
37
- "marked": "^11.1.1",
38
- "ora": "^7.0.1",
39
- "p-limit": "^4.0.0",
40
- "p-retry": "^5.1.2",
41
- "picocolors": "^1.0.0",
42
- "tiktoken": "^1.0.15",
43
- "web-tree-sitter": "^0.22.6"
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": "^8.57.0",
47
- "vitest": "^1.2.2"
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
- 'use strict';
1
+ /**
2
+ * File hash computation for incremental cache.
3
+ * Uses Node.js crypto SHA-256 to produce deterministic file fingerprints.
4
+ */
2
5
 
3
- const { createHash } = require('crypto');
4
- const { readFileSync } = require('fs');
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} hex string prefixed with 'sha256:'
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 };
@@ -1,47 +1,56 @@
1
- 'use strict';
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
- const { readFileSync, writeFileSync, mkdirSync, existsSync, appendFileSync } = require('fs');
4
- const path = require('path');
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 .legacyver-cache/hashes.json.
11
- * @param {string} cacheDir
12
- * @returns {Object} map of relativePath -> { hash, docFile, generatedAt }
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 = path.join(cacheDir, CACHE_FILE);
16
- if (!existsSync(cachePath)) return {};
17
+ export function loadCache(cacheDir) {
18
+ const cachePath = join(cacheDir, CACHE_FILE);
19
+ if (!existsSync(cachePath)) {
20
+ return {};
21
+ }
17
22
  try {
18
- return JSON.parse(readFileSync(cachePath, 'utf8'));
19
- } catch (e) {
20
- logger.warn(`Could not read cache: ${e.message}`);
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 .legacyver-cache/hashes.json.
27
- * @param {string} cacheDir
28
- * @param {Object} map
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, map) {
37
+ export function saveCache(cacheDir, cacheMap) {
31
38
  mkdirSync(cacheDir, { recursive: true });
32
- const cachePath = path.join(cacheDir, CACHE_FILE);
33
- writeFileSync(cachePath, JSON.stringify(map, null, 2), 'utf8');
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 files into cache hits and misses.
38
- * @param {Array} manifest FileManifest[]
39
- * @param {Object} cacheMap
40
- * @returns {{ hits: Array, misses: Array }}
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 {Object} cacheMap mutated in place
59
- * @param {string[]} currentPaths relative paths currently on disk
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 current = new Set(currentPaths);
63
- for (const key of Object.keys(cacheMap)) {
64
- if (!current.has(key)) {
65
- delete cacheMap[key];
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 it exists in projectRoot.
72
- * @param {string} projectRoot
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 autoAddToGitignore(projectRoot) {
75
- const gitignorePath = path.join(projectRoot, '.gitignore');
76
- if (!existsSync(gitignorePath)) return;
77
- const content = readFileSync(gitignorePath, 'utf8');
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
- module.exports = { loadCache, saveCache, getCacheHits, purgeDeleted, autoAddToGitignore };
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
+ }