legacyver 2.1.1 → 2.1.3
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/.legacyverrc +5 -10
- package/legacyver-docs/SUMMARY.md +3 -1
- package/legacyver-docs/components.md +55 -0
- package/legacyver-docs/config.md +30 -0
- package/legacyver-docs/errors.md +74 -0
- package/legacyver-docs/index.md +8 -204
- package/legacyver-docs/logger.md +83 -0
- package/package.json +1 -1
- package/src/cli/commands/analyze.js +15 -4
- package/src/cli/commands/init.js +13 -6
- package/src/cli/commands/providers.js +5 -0
- package/src/llm/free-model.js +17 -2
- package/src/llm/index.js +4 -1
- package/src/llm/prompts.js +96 -38
- package/src/llm/providers/gemini.js +94 -0
- package/src/llm/providers/groq.js +4 -2
- package/src/llm/providers/openrouter.js +1 -1
- package/src/llm/queue.js +12 -1
- package/src/utils/config.js +9 -1
package/.legacyverrc
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"provider": "
|
|
2
|
+
"provider": "openrouter",
|
|
3
|
+
"model": "meta-llama/llama-3.3-70b-instruct:free",
|
|
3
4
|
"format": "markdown",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"ignore": [
|
|
8
|
-
"test/**",
|
|
9
|
-
"**/*.test.*",
|
|
10
|
-
"**/node_modules/**"
|
|
11
|
-
]
|
|
12
|
-
}
|
|
5
|
+
"out": "./legacyver-docs",
|
|
6
|
+
"apiKey": "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"
|
|
7
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
The provided code is a collection of React components and a utility function written in TypeScript. It contains two React components, `Button` and `UserCard`, and a function `formatCurrency` for formatting currency.
|
|
3
|
+
|
|
4
|
+
## Functions
|
|
5
|
+
### Button
|
|
6
|
+
The `Button` component is a React functional component that takes in several props and returns a `button` element.
|
|
7
|
+
#### Parameters
|
|
8
|
+
| Name | Type | Description |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| label | string | The text to be displayed on the button |
|
|
11
|
+
| onClick | () => void | The function to be called when the button is clicked |
|
|
12
|
+
| disabled | boolean | Whether the button is disabled (optional) |
|
|
13
|
+
| variant | 'primary' | 'secondary' | 'danger' | The style variant of the button (optional) |
|
|
14
|
+
#### Return Value
|
|
15
|
+
The `Button` component returns a `button` element with the specified props.
|
|
16
|
+
|
|
17
|
+
### UserCard
|
|
18
|
+
The `UserCard` component is a React functional component that takes in several props and returns a user card.
|
|
19
|
+
#### Parameters
|
|
20
|
+
| Name | Type | Description |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| userId | number | The ID of the user |
|
|
23
|
+
| onClose | () => void | The function to be called when the close button is clicked |
|
|
24
|
+
#### Return Value
|
|
25
|
+
The `UserCard` component returns a `div` element containing the user's information, or a loading message if the data is not available.
|
|
26
|
+
|
|
27
|
+
### formatCurrency
|
|
28
|
+
The `formatCurrency` function formats a given amount as a currency string.
|
|
29
|
+
#### Parameters
|
|
30
|
+
| Name | Type | Description |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| amount | number | The amount to be formatted |
|
|
33
|
+
| currency | string | The currency of the amount (optional, defaults to 'USD') |
|
|
34
|
+
#### Return Value
|
|
35
|
+
The `formatCurrency` function returns a string representing the formatted currency.
|
|
36
|
+
|
|
37
|
+
## Dependencies
|
|
38
|
+
* React
|
|
39
|
+
* Intl.NumberFormat (for currency formatting)
|
|
40
|
+
|
|
41
|
+
## Usage Example
|
|
42
|
+
No clear usage pattern is visible in the provided code. However, the components and function can be used as follows:
|
|
43
|
+
```tsx
|
|
44
|
+
import { Button, UserCard, formatCurrency } from './components';
|
|
45
|
+
|
|
46
|
+
const Example = () => {
|
|
47
|
+
return (
|
|
48
|
+
<div>
|
|
49
|
+
<Button label="Click me" onClick={() => console.log('Button clicked')} />
|
|
50
|
+
<UserCard userId={1} onClose={() => console.log('User card closed')} />
|
|
51
|
+
<p>Formatted currency: {formatCurrency(1000, 'USD')}</p>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
This is a JavaScript module that exports a single function `loadConfig`, which loads configuration from a file and merges with CLI flags.
|
|
3
|
+
|
|
4
|
+
## Functions
|
|
5
|
+
### `loadConfig`
|
|
6
|
+
|
|
7
|
+
#### Description:
|
|
8
|
+
Load configuration from file and merge with CLI flags.
|
|
9
|
+
CLI flags always win over file config.
|
|
10
|
+
|
|
11
|
+
#### Params Table:
|
|
12
|
+
|
|
13
|
+
| Name | Type |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `cliFlags` | Object |
|
|
16
|
+
|
|
17
|
+
#### Return Value:
|
|
18
|
+
Object
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
function loadConfig(cliFlags = {}) {
|
|
22
|
+
// ...
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Dependencies
|
|
27
|
+
* `cosmiconfig: cosmiconfigSync`
|
|
28
|
+
|
|
29
|
+
## Usage Example
|
|
30
|
+
No clear pattern is visible in the code to demonstrate usage.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
This file exports five custom error classes for use in a Legacyver application.
|
|
3
|
+
|
|
4
|
+
## Functions
|
|
5
|
+
|
|
6
|
+
### `LegacyverError`
|
|
7
|
+
#### Description
|
|
8
|
+
A base class for all custom errors in Legacyver.
|
|
9
|
+
#### Parameters
|
|
10
|
+
| Name | Type | Default Value |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| message | string | |
|
|
13
|
+
| code | string | 'LEGACYVER_ERROR' |
|
|
14
|
+
|
|
15
|
+
#### Return Value
|
|
16
|
+
None.
|
|
17
|
+
|
|
18
|
+
### `NoApiKeyError`
|
|
19
|
+
#### Description
|
|
20
|
+
Raised when no API key is found for a provider.
|
|
21
|
+
#### Parameters
|
|
22
|
+
| Name | Type | Required | Default Value |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| provider | string | | |
|
|
25
|
+
|
|
26
|
+
#### Return Value
|
|
27
|
+
None.
|
|
28
|
+
|
|
29
|
+
#### Detected Patterns:
|
|
30
|
+
- The error message includes a suggestion to set the `OPENROUTER_API_KEY` environment variable or run `legacyver init`.
|
|
31
|
+
- The error message includes a link to obtain an API key at https://openrouter.ai/keys.
|
|
32
|
+
|
|
33
|
+
### `RateLimitError`
|
|
34
|
+
#### Description
|
|
35
|
+
Raised when the rate limit is exceeded for a provider.
|
|
36
|
+
#### Parameters
|
|
37
|
+
| Name | Type | Required | Default Value |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| provider | string | | |
|
|
40
|
+
| retryAfter | number | | 1000 |
|
|
41
|
+
|
|
42
|
+
#### Return Value
|
|
43
|
+
None.
|
|
44
|
+
|
|
45
|
+
#### Detected Patterns:
|
|
46
|
+
- The error message includes a suggestion to retry.
|
|
47
|
+
- The error message indicates the amount of time that must pass before retrying (1000ms by default).
|
|
48
|
+
|
|
49
|
+
### `ParseError`
|
|
50
|
+
#### Description
|
|
51
|
+
Raised when there is an issue parsing a file.
|
|
52
|
+
#### Parameters
|
|
53
|
+
| Name | Type | Required | Default Value |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| filePath | string | | |
|
|
56
|
+
| originalError | Error | | |
|
|
57
|
+
|
|
58
|
+
#### Return Value
|
|
59
|
+
None.
|
|
60
|
+
|
|
61
|
+
### `RenderError`
|
|
62
|
+
#### Description
|
|
63
|
+
Raised when there is an issue rendering a format.
|
|
64
|
+
#### Parameters
|
|
65
|
+
| Name | Type | Required | Default Value |
|
|
66
|
+
| --- | --- | --- | --- |
|
|
67
|
+
| format | string | | |
|
|
68
|
+
| originalError | Error | | |
|
|
69
|
+
|
|
70
|
+
#### Return Value
|
|
71
|
+
None.
|
|
72
|
+
|
|
73
|
+
## Dependencies
|
|
74
|
+
* `Error`
|
package/legacyver-docs/index.md
CHANGED
|
@@ -1,213 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# utils — Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Primary language:** javascript
|
|
4
|
+
**Total files:** 3
|
|
5
|
+
**Analyzed at:** 2026-02-21T08:58:31.525Z
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Files
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
| Total Files | 66 |
|
|
11
|
-
| Analyzed At | 2026-02-21T06:45:30.827Z |
|
|
12
|
-
|
|
13
|
-
## Entry Points
|
|
14
|
-
|
|
15
|
-
- [bin/legacyver.js](bin/legacyver.md)
|
|
16
|
-
- [src/cache/hash.js](src/cache/hash.md)
|
|
17
|
-
- [src/cli/commands/version.js](src/cli/commands/version.md)
|
|
18
|
-
- [src/parser/ast/generic.js](src/parser/ast/generic.md)
|
|
19
|
-
- [src/parser/ast/go.js](src/parser/ast/go.md)
|
|
20
|
-
- [src/parser/ast/java.js](src/parser/ast/java.md)
|
|
21
|
-
- [src/parser/ast/typescript.js](src/parser/ast/typescript.md)
|
|
22
|
-
- [src/renderer/html.js](src/renderer/html.md)
|
|
23
|
-
- [src/renderer/json.js](src/renderer/json.md)
|
|
24
|
-
- [src/renderer/markdown.js](src/renderer/markdown.md)
|
|
25
|
-
- [test/chunker.test.js](test/chunker.test.md)
|
|
26
|
-
- [test/complexity-scorer.test.js](test/complexity-scorer.test.md)
|
|
27
|
-
- [test/crawler.test.js](test/crawler.test.md)
|
|
28
|
-
- [test/fixtures/js-express/src/app.js](test/fixtures/js-express/src/app.md)
|
|
29
|
-
- [test/fixtures/python-flask/app.py](test/fixtures/python-flask/app.md)
|
|
30
|
-
- [test/fixtures/ts-react/src/components.tsx](test/fixtures/ts-react/src/components.md)
|
|
31
|
-
- [test/integration.laravel.test.js](test/integration.laravel.test.md)
|
|
32
|
-
- [test/integration.test.js](test/integration.test.md)
|
|
33
|
-
- [test/parser.laravel.test.js](test/parser.laravel.test.md)
|
|
34
|
-
- [test/parser.test.js](test/parser.test.md)
|
|
35
|
-
- [test/providers.openrouter.test.js](test/providers.openrouter.test.md)
|
|
36
|
-
- [test/validator.test.js](test/validator.test.md)
|
|
9
|
+
- [errors.js](errors.md)
|
|
10
|
+
- [config.js](config.md)
|
|
11
|
+
- [logger.js](logger.md)
|
|
37
12
|
|
|
38
13
|
## Dependency Graph
|
|
39
14
|
|
|
40
15
|
```mermaid
|
|
41
16
|
graph TD
|
|
42
|
-
N0["bin/legacyver.js"] --> N1["src/cli/commands/analyze.js"]
|
|
43
|
-
N0["bin/legacyver.js"] --> N2["src/cli/commands/init.js"]
|
|
44
|
-
N0["bin/legacyver.js"] --> N3["src/cli/commands/providers.js"]
|
|
45
|
-
N0["bin/legacyver.js"] --> N4["src/cli/commands/cache.js"]
|
|
46
|
-
N5["src/cache/index.js"] --> N6["src/utils/logger.js"]
|
|
47
|
-
N1["src/cli/commands/analyze.js"] --> N7["src/utils/config.js"]
|
|
48
|
-
N1["src/cli/commands/analyze.js"] --> N8["src/cli/ui.js"]
|
|
49
|
-
N1["src/cli/commands/analyze.js"] --> N6["src/utils/logger.js"]
|
|
50
|
-
N1["src/cli/commands/analyze.js"] --> N9["src/utils/errors.js"]
|
|
51
|
-
N1["src/cli/commands/analyze.js"] --> N10["src/crawler/index.js"]
|
|
52
|
-
N1["src/cli/commands/analyze.js"] --> N5["src/cache/index.js"]
|
|
53
|
-
N1["src/cli/commands/analyze.js"] --> N11["src/parser/index.js"]
|
|
54
|
-
N1["src/cli/commands/analyze.js"] --> N12["src/llm/cost-estimator.js"]
|
|
55
|
-
N1["src/cli/commands/analyze.js"] --> N13["src/llm/chunker.js"]
|
|
56
|
-
N1["src/cli/commands/analyze.js"] --> N14["src/llm/free-model.js"]
|
|
57
|
-
N1["src/cli/commands/analyze.js"] --> N15["src/llm/queue.js"]
|
|
58
|
-
N1["src/cli/commands/analyze.js"] --> N16["src/llm/index.js"]
|
|
59
|
-
N1["src/cli/commands/analyze.js"] --> N17["src/llm/validator.js"]
|
|
60
|
-
N1["src/cli/commands/analyze.js"] --> N18["src/llm/re-prompter.js"]
|
|
61
|
-
N1["src/cli/commands/analyze.js"] --> N19["src/renderer/index.js"]
|
|
62
|
-
N3["src/cli/commands/providers.js"] --> N6["src/utils/logger.js"]
|
|
63
|
-
N3["src/cli/commands/providers.js"] --> N7["src/utils/config.js"]
|
|
64
|
-
N10["src/crawler/index.js"] --> N20["src/crawler/walk.js"]
|
|
65
|
-
N10["src/crawler/index.js"] --> N21["src/crawler/manifest.js"]
|
|
66
|
-
N10["src/crawler/index.js"] --> N22["src/crawler/filters.js"]
|
|
67
|
-
N10["src/crawler/index.js"] --> N6["src/utils/logger.js"]
|
|
68
|
-
N21["src/crawler/manifest.js"] --> N22["src/crawler/filters.js"]
|
|
69
|
-
N20["src/crawler/walk.js"] --> N22["src/crawler/filters.js"]
|
|
70
|
-
N13["src/llm/chunker.js"] --> N23["src/llm/prompts.js"]
|
|
71
|
-
N13["src/llm/chunker.js"] --> N12["src/llm/cost-estimator.js"]
|
|
72
|
-
N13["src/llm/chunker.js"] --> N6["src/utils/logger.js"]
|
|
73
|
-
N12["src/llm/cost-estimator.js"] --> N6["src/utils/logger.js"]
|
|
74
|
-
N14["src/llm/free-model.js"] --> N6["src/utils/logger.js"]
|
|
75
|
-
N16["src/llm/index.js"] --> N24["src/llm/providers/openrouter.js"]
|
|
76
|
-
N16["src/llm/index.js"] --> N25["src/llm/providers/ollama.js"]
|
|
77
|
-
N16["src/llm/index.js"] --> N26["src/llm/providers/groq.js"]
|
|
78
|
-
N26["src/llm/providers/groq.js"] --> N9["src/utils/errors.js"]
|
|
79
|
-
N25["src/llm/providers/ollama.js"] --> N6["src/utils/logger.js"]
|
|
80
|
-
N24["src/llm/providers/openrouter.js"] --> N9["src/utils/errors.js"]
|
|
81
|
-
N24["src/llm/providers/openrouter.js"] --> N6["src/utils/logger.js"]
|
|
82
|
-
N15["src/llm/queue.js"] --> N6["src/utils/logger.js"]
|
|
83
|
-
N18["src/llm/re-prompter.js"] --> N23["src/llm/prompts.js"]
|
|
84
|
-
N18["src/llm/re-prompter.js"] --> N6["src/utils/logger.js"]
|
|
85
|
-
N17["src/llm/validator.js"] --> N6["src/utils/logger.js"]
|
|
86
|
-
N27["src/parser/ast/generic.js"] --> N6["src/utils/logger.js"]
|
|
87
|
-
N28["src/parser/ast/go.js"] --> N29["src/parser/complexity-scorer.js"]
|
|
88
|
-
N28["src/parser/ast/go.js"] --> N30["src/parser/body-extractor.js"]
|
|
89
|
-
N31["src/parser/ast/java.js"] --> N29["src/parser/complexity-scorer.js"]
|
|
90
|
-
N31["src/parser/ast/java.js"] --> N30["src/parser/body-extractor.js"]
|
|
91
|
-
N32["src/parser/ast/javascript.js"] --> N29["src/parser/complexity-scorer.js"]
|
|
92
|
-
N32["src/parser/ast/javascript.js"] --> N30["src/parser/body-extractor.js"]
|
|
93
|
-
N33["src/parser/ast/laravel/index.js"] --> N34["src/parser/ast/laravel/classifier.js"]
|
|
94
|
-
N33["src/parser/ast/laravel/index.js"] --> N35["src/parser/ast/laravel/controller.js"]
|
|
95
|
-
N33["src/parser/ast/laravel/index.js"] --> N36["src/parser/ast/laravel/model.js"]
|
|
96
|
-
N33["src/parser/ast/laravel/index.js"] --> N37["src/parser/ast/laravel/routes.js"]
|
|
97
|
-
N33["src/parser/ast/laravel/index.js"] --> N38["src/parser/ast/laravel/blade.js"]
|
|
98
|
-
N33["src/parser/ast/laravel/index.js"] --> N39["src/parser/ast/laravel/provider.js"]
|
|
99
|
-
N40["src/parser/ast/php.js"] --> N29["src/parser/complexity-scorer.js"]
|
|
100
|
-
N40["src/parser/ast/php.js"] --> N30["src/parser/body-extractor.js"]
|
|
101
|
-
N41["src/parser/ast/python.js"] --> N29["src/parser/complexity-scorer.js"]
|
|
102
|
-
N41["src/parser/ast/python.js"] --> N30["src/parser/body-extractor.js"]
|
|
103
|
-
N42["src/parser/ast/typescript.js"] --> N32["src/parser/ast/javascript.js"]
|
|
104
|
-
N29["src/parser/complexity-scorer.js"] --> N43["src/parser/pattern-detector.js"]
|
|
105
|
-
N11["src/parser/index.js"] --> N6["src/utils/logger.js"]
|
|
106
|
-
N11["src/parser/index.js"] --> N33["src/parser/ast/laravel/index.js"]
|
|
107
|
-
N11["src/parser/index.js"] --> N44["src/parser/call-graph.js"]
|
|
108
|
-
N11["src/parser/index.js"] --> N45["src/parser/pkg-builder.js"]
|
|
109
|
-
N19["src/renderer/index.js"] --> N9["src/utils/errors.js"]
|
|
110
|
-
N46["test/chunker.test.js"] --> N13["src/llm/chunker.js"]
|
|
111
|
-
N47["test/complexity-scorer.test.js"] --> N29["src/parser/complexity-scorer.js"]
|
|
112
|
-
N47["test/complexity-scorer.test.js"] --> N43["src/parser/pattern-detector.js"]
|
|
113
|
-
N47["test/complexity-scorer.test.js"] --> N30["src/parser/body-extractor.js"]
|
|
114
|
-
N48["test/crawler.test.js"] --> N10["src/crawler/index.js"]
|
|
115
|
-
N49["test/fixtures/js-express/src/app.js"] --> N50["test/fixtures/js-express/src/routes/users.js"]
|
|
116
|
-
N49["test/fixtures/js-express/src/app.js"] --> N51["test/fixtures/js-express/src/middleware/auth.js"]
|
|
117
|
-
N50["test/fixtures/js-express/src/routes/users.js"] --> N52["test/fixtures/js-express/src/utils/db.js"]
|
|
118
|
-
N53["test/integration.laravel.test.js"] --> N10["src/crawler/index.js"]
|
|
119
|
-
N53["test/integration.laravel.test.js"] --> N11["src/parser/index.js"]
|
|
120
|
-
N53["test/integration.laravel.test.js"] --> N13["src/llm/chunker.js"]
|
|
121
|
-
N53["test/integration.laravel.test.js"] --> N19["src/renderer/index.js"]
|
|
122
|
-
N54["test/integration.test.js"] --> N10["src/crawler/index.js"]
|
|
123
|
-
N54["test/integration.test.js"] --> N11["src/parser/index.js"]
|
|
124
|
-
N54["test/integration.test.js"] --> N13["src/llm/chunker.js"]
|
|
125
|
-
N54["test/integration.test.js"] --> N19["src/renderer/index.js"]
|
|
126
|
-
N55["test/parser.laravel.test.js"] --> N40["src/parser/ast/php.js"]
|
|
127
|
-
N55["test/parser.laravel.test.js"] --> N33["src/parser/ast/laravel/index.js"]
|
|
128
|
-
N55["test/parser.laravel.test.js"] --> N34["src/parser/ast/laravel/classifier.js"]
|
|
129
|
-
N55["test/parser.laravel.test.js"] --> N37["src/parser/ast/laravel/routes.js"]
|
|
130
|
-
N55["test/parser.laravel.test.js"] --> N36["src/parser/ast/laravel/model.js"]
|
|
131
|
-
N55["test/parser.laravel.test.js"] --> N35["src/parser/ast/laravel/controller.js"]
|
|
132
|
-
N56["test/parser.test.js"] --> N32["src/parser/ast/javascript.js"]
|
|
133
|
-
N56["test/parser.test.js"] --> N40["src/parser/ast/php.js"]
|
|
134
|
-
N56["test/parser.test.js"] --> N41["src/parser/ast/python.js"]
|
|
135
|
-
N56["test/parser.test.js"] --> N33["src/parser/ast/laravel/index.js"]
|
|
136
|
-
N57["test/providers.openrouter.test.js"] --> N24["src/llm/providers/openrouter.js"]
|
|
137
|
-
N57["test/providers.openrouter.test.js"] --> N9["src/utils/errors.js"]
|
|
138
|
-
N58["test/validator.test.js"] --> N17["src/llm/validator.js"]
|
|
139
17
|
```
|
|
140
|
-
|
|
141
|
-
## Module Index
|
|
142
|
-
|
|
143
|
-
| File | Language | Lines | Functions | Classes |
|
|
144
|
-
|---|---|---|---|---|
|
|
145
|
-
| [bin/legacyver.js](bin/legacyver.md) | javascript | 48 | 1 | 0 |
|
|
146
|
-
| [src/cache/hash.js](src/cache/hash.md) | javascript | 13 | 1 | 0 |
|
|
147
|
-
| [src/cache/index.js](src/cache/index.md) | javascript | 76 | 6 | 0 |
|
|
148
|
-
| [src/cli/commands/analyze.js](src/cli/commands/analyze.md) | javascript | 218 | 11 | 0 |
|
|
149
|
-
| [src/cli/commands/cache.js](src/cli/commands/cache.md) | javascript | 13 | 2 | 0 |
|
|
150
|
-
| [src/cli/commands/init.js](src/cli/commands/init.md) | javascript | 60 | 8 | 0 |
|
|
151
|
-
| [src/cli/commands/providers.js](src/cli/commands/providers.md) | javascript | 46 | 1 | 0 |
|
|
152
|
-
| [src/cli/commands/version.js](src/cli/commands/version.md) | javascript | 7 | 1 | 0 |
|
|
153
|
-
| [src/cli/ui.js](src/cli/ui.md) | javascript | 78 | 6 | 0 |
|
|
154
|
-
| [src/crawler/filters.js](src/crawler/filters.md) | javascript | 52 | 2 | 0 |
|
|
155
|
-
| [src/crawler/index.js](src/crawler/index.md) | javascript | 55 | 4 | 0 |
|
|
156
|
-
| [src/crawler/manifest.js](src/crawler/manifest.md) | javascript | 34 | 1 | 0 |
|
|
157
|
-
| [src/crawler/walk.js](src/crawler/walk.md) | javascript | 43 | 1 | 0 |
|
|
158
|
-
| [src/llm/chunker.js](src/llm/chunker.md) | javascript | 46 | 2 | 0 |
|
|
159
|
-
| [src/llm/cost-estimator.js](src/llm/cost-estimator.md) | javascript | 74 | 10 | 0 |
|
|
160
|
-
| [src/llm/free-model.js](src/llm/free-model.md) | javascript | 42 | 1 | 0 |
|
|
161
|
-
| [src/llm/index.js](src/llm/index.md) | javascript | 22 | 4 | 0 |
|
|
162
|
-
| [src/llm/prompts.js](src/llm/prompts.md) | javascript | 51 | 2 | 0 |
|
|
163
|
-
| [src/llm/providers/groq.js](src/llm/providers/groq.md) | javascript | 54 | 7 | 1 |
|
|
164
|
-
| [src/llm/providers/ollama.js](src/llm/providers/ollama.md) | javascript | 48 | 4 | 1 |
|
|
165
|
-
| [src/llm/providers/openrouter.js](src/llm/providers/openrouter.md) | javascript | 56 | 6 | 1 |
|
|
166
|
-
| [src/llm/queue.js](src/llm/queue.md) | javascript | 53 | 1 | 0 |
|
|
167
|
-
| [src/llm/re-prompter.js](src/llm/re-prompter.md) | javascript | 35 | 1 | 0 |
|
|
168
|
-
| [src/llm/validator.js](src/llm/validator.md) | javascript | 61 | 11 | 1 |
|
|
169
|
-
| [src/parser/ast/generic.js](src/parser/ast/generic.md) | javascript | 62 | 1 | 0 |
|
|
170
|
-
| [src/parser/ast/go.js](src/parser/ast/go.md) | javascript | 105 | 5 | 0 |
|
|
171
|
-
| [src/parser/ast/java.js](src/parser/ast/java.md) | javascript | 92 | 5 | 0 |
|
|
172
|
-
| [src/parser/ast/javascript.js](src/parser/ast/javascript.md) | javascript | 190 | 6 | 0 |
|
|
173
|
-
| [src/parser/ast/laravel/blade.js](src/parser/ast/laravel/blade.md) | javascript | 50 | 1 | 0 |
|
|
174
|
-
| [src/parser/ast/laravel/classifier.js](src/parser/ast/laravel/classifier.md) | javascript | 24 | 1 | 0 |
|
|
175
|
-
| [src/parser/ast/laravel/controller.js](src/parser/ast/laravel/controller.md) | javascript | 30 | 2 | 0 |
|
|
176
|
-
| [src/parser/ast/laravel/index.js](src/parser/ast/laravel/index.md) | javascript | 47 | 2 | 0 |
|
|
177
|
-
| [src/parser/ast/laravel/model.js](src/parser/ast/laravel/model.md) | javascript | 34 | 2 | 0 |
|
|
178
|
-
| [src/parser/ast/laravel/provider.js](src/parser/ast/laravel/provider.md) | javascript | 23 | 1 | 0 |
|
|
179
|
-
| [src/parser/ast/laravel/routes.js](src/parser/ast/laravel/routes.md) | javascript | 37 | 1 | 0 |
|
|
180
|
-
| [src/parser/ast/php.js](src/parser/ast/php.md) | javascript | 115 | 6 | 0 |
|
|
181
|
-
| [src/parser/ast/python.js](src/parser/ast/python.md) | javascript | 93 | 5 | 0 |
|
|
182
|
-
| [src/parser/ast/typescript.js](src/parser/ast/typescript.md) | javascript | 13 | 1 | 0 |
|
|
183
|
-
| [src/parser/body-extractor.js](src/parser/body-extractor.md) | javascript | 33 | 2 | 0 |
|
|
184
|
-
| [src/parser/call-graph.js](src/parser/call-graph.md) | javascript | 60 | 5 | 0 |
|
|
185
|
-
| [src/parser/complexity-scorer.js](src/parser/complexity-scorer.md) | javascript | 47 | 1 | 0 |
|
|
186
|
-
| [src/parser/index.js](src/parser/index.md) | javascript | 67 | 4 | 0 |
|
|
187
|
-
| [src/parser/pattern-detector.js](src/parser/pattern-detector.md) | javascript | 67 | 1 | 0 |
|
|
188
|
-
| [src/parser/pkg-builder.js](src/parser/pkg-builder.md) | javascript | 51 | 2 | 0 |
|
|
189
|
-
| [src/renderer/html.js](src/renderer/html.md) | javascript | 72 | 1 | 0 |
|
|
190
|
-
| [src/renderer/index.js](src/renderer/index.md) | javascript | 27 | 5 | 0 |
|
|
191
|
-
| [src/renderer/json.js](src/renderer/json.md) | javascript | 23 | 1 | 0 |
|
|
192
|
-
| [src/renderer/markdown.js](src/renderer/markdown.md) | javascript | 99 | 4 | 0 |
|
|
193
|
-
| [src/utils/config.js](src/utils/config.md) | javascript | 49 | 1 | 0 |
|
|
194
|
-
| [src/utils/errors.js](src/utils/errors.md) | javascript | 48 | 5 | 5 |
|
|
195
|
-
| [src/utils/logger.js](src/utils/logger.md) | javascript | 35 | 7 | 0 |
|
|
196
|
-
| [test/chunker.test.js](test/chunker.test.md) | javascript | 58 | 0 | 0 |
|
|
197
|
-
| [test/complexity-scorer.test.js](test/complexity-scorer.test.md) | javascript | 101 | 7 | 0 |
|
|
198
|
-
| [test/crawler.test.js](test/crawler.test.md) | javascript | 54 | 6 | 0 |
|
|
199
|
-
| [test/fixtures/js-express/src/app.js](test/fixtures/js-express/src/app.md) | javascript | 12 | 0 | 0 |
|
|
200
|
-
| [test/fixtures/js-express/src/middleware/auth.js](test/fixtures/js-express/src/middleware/auth.md) | javascript | 14 | 1 | 0 |
|
|
201
|
-
| [test/fixtures/js-express/src/routes/users.js](test/fixtures/js-express/src/routes/users.md) | javascript | 24 | 4 | 0 |
|
|
202
|
-
| [test/fixtures/js-express/src/utils/db.js](test/fixtures/js-express/src/utils/db.md) | javascript | 35 | 8 | 0 |
|
|
203
|
-
| [test/fixtures/python-flask/app.py](test/fixtures/python-flask/app.md) | python | 24 | 10 | 0 |
|
|
204
|
-
| [test/fixtures/ts-react/src/components.tsx](test/fixtures/ts-react/src/components.md) | typescript | 46 | 1 | 0 |
|
|
205
|
-
| [test/integration.laravel.test.js](test/integration.laravel.test.md) | javascript | 100 | 5 | 0 |
|
|
206
|
-
| [test/integration.test.js](test/integration.test.md) | javascript | 100 | 5 | 0 |
|
|
207
|
-
| [test/parser.laravel.test.js](test/parser.laravel.test.md) | javascript | 130 | 0 | 2 |
|
|
208
|
-
| [test/parser.test.js](test/parser.test.md) | javascript | 137 | 6 | 2 |
|
|
209
|
-
| [test/providers.openrouter.test.js](test/providers.openrouter.test.md) | javascript | 177 | 20 | 0 |
|
|
210
|
-
| [test/validator.test.js](test/validator.test.md) | javascript | 59 | 0 | 0 |
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
*Generated by [Legacyver](https://github.com/legacyver/legacyver)*
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
This module provides a logging system with configurable log levels and colorful output.
|
|
3
|
+
|
|
4
|
+
## Functions
|
|
5
|
+
|
|
6
|
+
### `setLevel(level)`
|
|
7
|
+
#### Params | Description
|
|
8
|
+
----------|-------------
|
|
9
|
+
`level` | The desired log level
|
|
10
|
+
|
|
11
|
+
#### Returns
|
|
12
|
+
None
|
|
13
|
+
|
|
14
|
+
Sets the current log level to the specified `level`.
|
|
15
|
+
|
|
16
|
+
### `setCI(val)`
|
|
17
|
+
#### Params | Description
|
|
18
|
+
----------|-------------
|
|
19
|
+
`val` | A boolean indicating whether CI mode is enabled
|
|
20
|
+
|
|
21
|
+
#### Returns
|
|
22
|
+
None
|
|
23
|
+
|
|
24
|
+
Toggles the CI mode on or off.
|
|
25
|
+
|
|
26
|
+
### `shouldLog(level)`
|
|
27
|
+
#### Params | Description
|
|
28
|
+
----------|-------------
|
|
29
|
+
`level` | The log level to check for
|
|
30
|
+
|
|
31
|
+
#### Returns
|
|
32
|
+
Boolean
|
|
33
|
+
Determines whether the current log level allows logging at the specified `level`.
|
|
34
|
+
|
|
35
|
+
### `debug(...args)`
|
|
36
|
+
#### Params | Description
|
|
37
|
+
----------|-------------
|
|
38
|
+
`...args` | Variable number of arguments to be logged as debug output
|
|
39
|
+
|
|
40
|
+
#### Returns
|
|
41
|
+
None
|
|
42
|
+
Logs the provided `args` to the console with a gray '[debug]' prefix if the current log level allows it.
|
|
43
|
+
|
|
44
|
+
### `info(...args)`
|
|
45
|
+
#### Params | Description
|
|
46
|
+
----------|-------------
|
|
47
|
+
`...args` | Variable number of arguments to be logged as info output
|
|
48
|
+
|
|
49
|
+
#### Returns
|
|
50
|
+
None
|
|
51
|
+
Logs the provided `args` to the console with a cyan '[info]' prefix if the current log level allows it.
|
|
52
|
+
|
|
53
|
+
### `warn(...args)`
|
|
54
|
+
#### Params | Description
|
|
55
|
+
----------|-------------
|
|
56
|
+
`...args` | Variable number of arguments to be logged as warn output
|
|
57
|
+
|
|
58
|
+
#### Returns
|
|
59
|
+
None
|
|
60
|
+
Logs the provided `args` to the console with a yellow '[warn]' prefix if the current log level allows it.
|
|
61
|
+
|
|
62
|
+
### `error(...args)`
|
|
63
|
+
#### Params | Description
|
|
64
|
+
----------|-------------
|
|
65
|
+
`...args` | Variable number of arguments to be logged as error output
|
|
66
|
+
|
|
67
|
+
#### Returns
|
|
68
|
+
None
|
|
69
|
+
Logs the provided `args` to the console with a red '[error]' prefix if the current log level allows it.
|
|
70
|
+
|
|
71
|
+
## Dependencies
|
|
72
|
+
* picocolors (`pc`)
|
|
73
|
+
|
|
74
|
+
## Usage Example
|
|
75
|
+
```javascript
|
|
76
|
+
const logger = require('./logger');
|
|
77
|
+
|
|
78
|
+
logger.debug('This is a debug message');
|
|
79
|
+
logger.info('This is an info message');
|
|
80
|
+
logger.warn('This is a warn message');
|
|
81
|
+
logger.error('This is an error message');
|
|
82
|
+
```
|
|
83
|
+
Note: This example demonstrates the usage of the `debug`, `info`, `warn`, and `error` functions, which are exported from the logger module.
|
package/package.json
CHANGED
|
@@ -144,8 +144,11 @@ module.exports = async function analyzeCommand(target, flags) {
|
|
|
144
144
|
provider = createProvider(config);
|
|
145
145
|
} catch (e) {
|
|
146
146
|
if (e.code === 'NO_API_KEY') {
|
|
147
|
-
const
|
|
148
|
-
|
|
147
|
+
const providerName = (config.provider || '').toLowerCase();
|
|
148
|
+
const isGroq = providerName === 'groq';
|
|
149
|
+
const isGemini = providerName === 'gemini';
|
|
150
|
+
const label = isGemini ? 'Google Gemini' : isGroq ? 'Groq' : 'OpenRouter';
|
|
151
|
+
console.error(pc.red(`\n No API key found for ${label}.\n`));
|
|
149
152
|
console.error(' To fix, choose one of:\n');
|
|
150
153
|
if (isGroq) {
|
|
151
154
|
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
@@ -153,14 +156,22 @@ module.exports = async function analyzeCommand(target, flags) {
|
|
|
153
156
|
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
154
157
|
console.error(' export GROQ_API_KEY=your_key_here\n');
|
|
155
158
|
console.error(' Get a free Groq key at: https://console.groq.com/keys\n');
|
|
159
|
+
} else if (isGemini) {
|
|
160
|
+
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
161
|
+
console.error(' legacyver init\n');
|
|
162
|
+
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
163
|
+
console.error(' export GEMINI_API_KEY=your_key_here\n');
|
|
164
|
+
console.error(' Get a free key at: https://aistudio.google.com/apikey\n');
|
|
156
165
|
} else {
|
|
157
166
|
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
158
167
|
console.error(' legacyver init\n');
|
|
159
168
|
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
160
169
|
console.error(' export OPENROUTER_API_KEY=your_key_here\n');
|
|
161
|
-
console.error(pc.cyan(' 3. Use
|
|
170
|
+
console.error(pc.cyan(' 3. Use Google Gemini instead (free, 15 req/min):'));
|
|
171
|
+
console.error(' legacyver analyze --provider gemini\n');
|
|
172
|
+
console.error(pc.cyan(' 4. Use Groq instead (fast & free):'));
|
|
162
173
|
console.error(' legacyver analyze --provider groq\n');
|
|
163
|
-
console.error(pc.cyan('
|
|
174
|
+
console.error(pc.cyan(' 5. Use local Ollama instead (no key needed):'));
|
|
164
175
|
console.error(' legacyver analyze --provider ollama\n');
|
|
165
176
|
console.error(' Get a free OpenRouter key at: https://openrouter.ai/keys\n');
|
|
166
177
|
}
|
package/src/cli/commands/init.js
CHANGED
|
@@ -24,21 +24,24 @@ module.exports = async function initCommand() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const providerRaw = await ask(rl, `LLM provider [openrouter/groq/ollama] (default: openrouter): `);
|
|
27
|
+
const providerRaw = await ask(rl, `LLM provider [openrouter/gemini/groq/ollama] (default: openrouter): `);
|
|
28
28
|
const providerChoice = providerRaw.trim() || 'openrouter';
|
|
29
29
|
const isOllama = providerChoice === 'ollama';
|
|
30
30
|
const isGroq = providerChoice === 'groq';
|
|
31
|
+
const isGemini = providerChoice === 'gemini';
|
|
31
32
|
|
|
32
33
|
const defaultModel = isOllama
|
|
33
34
|
? 'llama3.2'
|
|
34
35
|
: isGroq
|
|
35
36
|
? 'llama-3.3-70b-versatile'
|
|
36
|
-
:
|
|
37
|
+
: isGemini
|
|
38
|
+
? 'gemini-2.0-flash'
|
|
39
|
+
: 'meta-llama/llama-3.3-70b-instruct:free';
|
|
37
40
|
|
|
38
41
|
let apiKey = '';
|
|
39
42
|
if (!isOllama) {
|
|
40
|
-
const keyLabel = isGroq ? 'Groq' : 'OpenRouter';
|
|
41
|
-
const keyHint = isGroq ? 'https://console.groq.com/keys' : 'https://openrouter.ai/keys';
|
|
43
|
+
const keyLabel = isGemini ? 'Google Gemini' : isGroq ? 'Groq' : 'OpenRouter';
|
|
44
|
+
const keyHint = isGemini ? 'https://aistudio.google.com/apikey' : isGroq ? 'https://console.groq.com/keys' : 'https://openrouter.ai/keys';
|
|
42
45
|
apiKey = await ask(rl, `${keyLabel} API key (leave blank to set via env var — see ${keyHint}): `);
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -55,7 +58,9 @@ module.exports = async function initCommand() {
|
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
if (apiKey.trim()) {
|
|
58
|
-
if (
|
|
61
|
+
if (isGemini) {
|
|
62
|
+
config.geminiApiKey = apiKey.trim();
|
|
63
|
+
} else if (isGroq) {
|
|
59
64
|
config.groqApiKey = apiKey.trim();
|
|
60
65
|
} else {
|
|
61
66
|
config.apiKey = apiKey.trim();
|
|
@@ -69,6 +74,8 @@ module.exports = async function initCommand() {
|
|
|
69
74
|
? 'legacyver analyze --provider ollama'
|
|
70
75
|
: isGroq
|
|
71
76
|
? 'legacyver analyze --provider groq'
|
|
72
|
-
:
|
|
77
|
+
: isGemini
|
|
78
|
+
? 'legacyver analyze --provider gemini'
|
|
79
|
+
: 'legacyver analyze';
|
|
73
80
|
console.log(pc.cyan(`\nRun \`${exampleCmd}\` to generate documentation.`));
|
|
74
81
|
};
|
|
@@ -30,6 +30,11 @@ module.exports = async function providersCommand() {
|
|
|
30
30
|
console.log(' Status: ' + (process.env.GROQ_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
|
|
31
31
|
console.log(' Get a free key at: https://console.groq.com/keys');
|
|
32
32
|
console.log('');
|
|
33
|
+
console.log(pc.bold('Google Gemini') + ' (https://ai.google.dev)');
|
|
34
|
+
console.log(' Free tier: 15 req/min, 1,500 req/day. Set GEMINI_API_KEY env variable.');
|
|
35
|
+
console.log(' Status: ' + (process.env.GEMINI_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
|
|
36
|
+
console.log(' Get a free key at: https://aistudio.google.com/apikey');
|
|
37
|
+
console.log('');
|
|
33
38
|
|
|
34
39
|
console.log(pc.bold('Recommended Models (via OpenRouter):'));
|
|
35
40
|
console.log('');
|
package/src/llm/free-model.js
CHANGED
|
@@ -11,12 +11,27 @@ const pc = require('picocolors');
|
|
|
11
11
|
function applyFreeModelPolicy(config) {
|
|
12
12
|
const provider = (config.provider || 'openrouter').toLowerCase();
|
|
13
13
|
|
|
14
|
-
// Ollama and
|
|
15
|
-
if (provider === 'ollama' || provider === 'groq') {
|
|
14
|
+
// Ollama, Groq, and Gemini are always free — skip openrouter-specific logic
|
|
15
|
+
if (provider === 'ollama' || provider === 'groq' || provider === 'gemini') {
|
|
16
16
|
config.isFreeModel = true;
|
|
17
17
|
config.skipCostEstimation = true;
|
|
18
18
|
if (provider === 'groq') {
|
|
19
19
|
console.log(pc.cyan('[info] Using Groq — free tier. Rate limit: 30 req/min, 14,400 req/day'));
|
|
20
|
+
// Cap concurrency to 1 for Groq to avoid rate limits (30 req/min)
|
|
21
|
+
const requested = parseInt(config.concurrency) || 1;
|
|
22
|
+
if (requested > 1) {
|
|
23
|
+
logger.warn(`Groq free tier: capping concurrency from ${requested} to 1.`);
|
|
24
|
+
config.concurrency = 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (provider === 'gemini') {
|
|
28
|
+
console.log(pc.cyan('[info] Using Gemini — free tier. Rate limit: 15 req/min, 1,500 req/day'));
|
|
29
|
+
// Cap concurrency to 2 for Gemini (15 req/min is more generous)
|
|
30
|
+
const requested = parseInt(config.concurrency) || 1;
|
|
31
|
+
if (requested > 2) {
|
|
32
|
+
logger.warn(`Gemini free tier: capping concurrency from ${requested} to 2.`);
|
|
33
|
+
config.concurrency = 2;
|
|
34
|
+
}
|
|
20
35
|
}
|
|
21
36
|
return config;
|
|
22
37
|
}
|
package/src/llm/index.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
const { OpenRouterProvider } = require('./providers/openrouter');
|
|
4
4
|
const { OllamaProvider } = require('./providers/ollama');
|
|
5
5
|
const { GroqProvider } = require('./providers/groq');
|
|
6
|
+
const { GeminiProvider } = require('./providers/gemini');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Provider factory — returns the appropriate LLM adapter.
|
|
9
10
|
* @param {Object} config
|
|
10
|
-
* @returns {OpenRouterProvider|OllamaProvider|GroqProvider}
|
|
11
|
+
* @returns {OpenRouterProvider|OllamaProvider|GroqProvider|GeminiProvider}
|
|
11
12
|
*/
|
|
12
13
|
function createProvider(config) {
|
|
13
14
|
const provider = (config.provider || 'openrouter').toLowerCase();
|
|
@@ -16,6 +17,8 @@ function createProvider(config) {
|
|
|
16
17
|
return new OllamaProvider(config);
|
|
17
18
|
case 'groq':
|
|
18
19
|
return new GroqProvider(config);
|
|
20
|
+
case 'gemini':
|
|
21
|
+
return new GeminiProvider(config);
|
|
19
22
|
case 'openrouter':
|
|
20
23
|
default:
|
|
21
24
|
return new OpenRouterProvider(config);
|
package/src/llm/prompts.js
CHANGED
|
@@ -1,34 +1,98 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const SYSTEM_PROMPT = `You are a technical documentation writer.
|
|
4
|
-
1. Extracted structural facts about a source file (JSON)
|
|
5
|
-
2. The raw source code of that file
|
|
6
|
-
|
|
7
|
-
Your job is to write clear, accurate Markdown documentation based ONLY on what is present in the code.
|
|
3
|
+
const SYSTEM_PROMPT = `You are a technical documentation writer. Given a file's structure and source code, write accurate Markdown documentation based ONLY on what is present.
|
|
8
4
|
|
|
9
5
|
Rules:
|
|
10
|
-
- Do NOT infer behavior not
|
|
11
|
-
- Do NOT
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- Do not mention any function, class, parameter, or behavior that does not appear in the FileFacts JSON or bodySnippet above
|
|
15
|
-
- For each function with complexityClass "moderate" or "complex", explain the logic described in the bodySnippet in plain language
|
|
16
|
-
- For each function with detectedPatterns[] non-empty, explicitly describe what that pattern does in context
|
|
17
|
-
|
|
18
|
-
Temperature: use 0.1 — factual output only.
|
|
6
|
+
- Do NOT infer behavior not shown in the code
|
|
7
|
+
- Do NOT fabricate descriptions
|
|
8
|
+
- For complex functions, explain the logic from the bodySnippet
|
|
9
|
+
- For functions with detectedPatterns, describe what each pattern does
|
|
19
10
|
|
|
20
|
-
Output format
|
|
11
|
+
Output format:
|
|
21
12
|
## Overview
|
|
22
|
-
[1-2 sentences
|
|
13
|
+
[1-2 sentences]
|
|
23
14
|
|
|
24
15
|
## Functions
|
|
25
|
-
[
|
|
16
|
+
[### per function: description, params table, return value]
|
|
26
17
|
|
|
27
18
|
## Dependencies
|
|
28
|
-
[Bullet list of imports
|
|
19
|
+
[Bullet list of imports]
|
|
29
20
|
|
|
30
21
|
## Usage Example
|
|
31
|
-
[Only
|
|
22
|
+
[Only if a clear pattern is visible in the code]`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Strip null/undefined values and empty arrays from an object (shallow).
|
|
26
|
+
*/
|
|
27
|
+
function stripEmpty(obj) {
|
|
28
|
+
const result = {};
|
|
29
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
30
|
+
if (v === null || v === undefined) continue;
|
|
31
|
+
if (Array.isArray(v) && v.length === 0) continue;
|
|
32
|
+
result[k] = v;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a slim version of FileFacts for the prompt — remove noise, keep signal.
|
|
39
|
+
*/
|
|
40
|
+
function slimFacts(fileFacts) {
|
|
41
|
+
const slim = {
|
|
42
|
+
file: fileFacts.relativePath,
|
|
43
|
+
lang: fileFacts.language,
|
|
44
|
+
lines: fileFacts.linesOfCode,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Functions — only include useful fields
|
|
48
|
+
if (fileFacts.functions && fileFacts.functions.length > 0) {
|
|
49
|
+
slim.functions = fileFacts.functions.map(fn => {
|
|
50
|
+
const entry = { name: fn.name };
|
|
51
|
+
if (fn.params && fn.params.length > 0) {
|
|
52
|
+
entry.params = fn.params.map(p => p.type ? `${p.name}:${p.type}` : p.name);
|
|
53
|
+
}
|
|
54
|
+
if (fn.returnType) entry.returns = fn.returnType;
|
|
55
|
+
if (fn.isExported) entry.exported = true;
|
|
56
|
+
if (fn.isAsync) entry.async = true;
|
|
57
|
+
if (fn.complexityClass && fn.complexityClass !== 'simple') {
|
|
58
|
+
entry.complexity = fn.complexityClass;
|
|
59
|
+
}
|
|
60
|
+
if (fn.detectedPatterns && fn.detectedPatterns.length > 0) {
|
|
61
|
+
entry.patterns = fn.detectedPatterns;
|
|
62
|
+
}
|
|
63
|
+
if (fn.bodySnippet) entry.body = fn.bodySnippet;
|
|
64
|
+
if (fn.calls && fn.calls.length > 0) entry.calls = fn.calls;
|
|
65
|
+
return entry;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Classes
|
|
70
|
+
if (fileFacts.classes && fileFacts.classes.length > 0) {
|
|
71
|
+
slim.classes = fileFacts.classes.map(c => {
|
|
72
|
+
const entry = { name: c.name };
|
|
73
|
+
if (c.superClass) entry.extends = c.superClass;
|
|
74
|
+
if (c.methods && c.methods.length > 0) entry.methods = c.methods.map(m => m.name || m);
|
|
75
|
+
return entry;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Imports — compact
|
|
80
|
+
if (fileFacts.imports && fileFacts.imports.length > 0) {
|
|
81
|
+
slim.imports = fileFacts.imports.map(i => {
|
|
82
|
+
if (i.specifiers && i.specifiers.length > 0) {
|
|
83
|
+
return `${i.module}: ${i.specifiers.join(', ')}`;
|
|
84
|
+
}
|
|
85
|
+
return i.module;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Exports
|
|
90
|
+
if (fileFacts.exports && fileFacts.exports.length > 0) {
|
|
91
|
+
slim.exports = fileFacts.exports;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return slim;
|
|
95
|
+
}
|
|
32
96
|
|
|
33
97
|
/**
|
|
34
98
|
* Build the user message for a single file.
|
|
@@ -37,28 +101,22 @@ Output format (strict Markdown):
|
|
|
37
101
|
* @returns {string}
|
|
38
102
|
*/
|
|
39
103
|
function buildUserMessage(fileFacts, rawSource) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (fn.detectedPatterns && fn.detectedPatterns.length > 0) {
|
|
49
|
-
entry._patternHint = `This function uses patterns: ${fn.detectedPatterns.join(', ')}. Describe what each does in context.`;
|
|
50
|
-
}
|
|
51
|
-
return entry;
|
|
52
|
-
}),
|
|
53
|
-
};
|
|
104
|
+
const slim = slimFacts(fileFacts);
|
|
105
|
+
|
|
106
|
+
// Truncate source to ~150 lines to keep prompt small
|
|
107
|
+
const lines = rawSource.split('\n');
|
|
108
|
+
let source = rawSource;
|
|
109
|
+
if (lines.length > 150) {
|
|
110
|
+
source = lines.slice(0, 150).join('\n') + '\n// ... truncated';
|
|
111
|
+
}
|
|
54
112
|
|
|
55
|
-
return `
|
|
56
|
-
${JSON.stringify(
|
|
113
|
+
return `STRUCTURE:
|
|
114
|
+
${JSON.stringify(slim)}
|
|
57
115
|
|
|
58
|
-
|
|
59
|
-
${
|
|
116
|
+
CODE:
|
|
117
|
+
${source}
|
|
60
118
|
|
|
61
|
-
Generate documentation
|
|
119
|
+
Generate documentation following the system instructions.`;
|
|
62
120
|
}
|
|
63
121
|
|
|
64
122
|
module.exports = { SYSTEM_PROMPT, buildUserMessage };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { NoApiKeyError } = require('../../utils/errors');
|
|
4
|
+
|
|
5
|
+
const GEMINI_BASE = 'https://generativelanguage.googleapis.com/v1beta';
|
|
6
|
+
const DEFAULT_MODEL = 'gemini-2.0-flash';
|
|
7
|
+
|
|
8
|
+
class GeminiProvider {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.apiKey = process.env.GEMINI_API_KEY || config.geminiApiKey;
|
|
11
|
+
if (!this.apiKey) throw new NoApiKeyError('gemini');
|
|
12
|
+
this.model = config.model || DEFAULT_MODEL;
|
|
13
|
+
this.name = 'gemini';
|
|
14
|
+
this.isFreeModel = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async complete(chunk) {
|
|
18
|
+
const url = `${GEMINI_BASE}/models/${this.model}:generateContent?key=${this.apiKey}`;
|
|
19
|
+
|
|
20
|
+
const body = {
|
|
21
|
+
contents: [
|
|
22
|
+
{
|
|
23
|
+
parts: [
|
|
24
|
+
{ text: chunk.systemPrompt + '\n\n' + chunk.userMessage },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
generationConfig: {
|
|
29
|
+
temperature: 0.3,
|
|
30
|
+
maxOutputTokens: 4096,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await fetch(url, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(body),
|
|
40
|
+
});
|
|
41
|
+
} catch (e) {
|
|
42
|
+
throw new Error(`Could not connect to Gemini API: ${e.message}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (response.status === 429) {
|
|
46
|
+
const body = await response.text();
|
|
47
|
+
const { RateLimitError } = require('../../utils/errors');
|
|
48
|
+
const headerVal = parseInt(response.headers.get('retry-after') || '0');
|
|
49
|
+
const retryAfter = Math.max(headerVal * 1000, 15000);
|
|
50
|
+
const err = new RateLimitError('gemini', retryAfter);
|
|
51
|
+
// Include Gemini's actual error detail so user can see what's wrong
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(body);
|
|
54
|
+
const detail = parsed.error && parsed.error.message ? parsed.error.message : body.substring(0, 200);
|
|
55
|
+
err.message = `Rate limit exceeded for provider "gemini": ${detail}`;
|
|
56
|
+
} catch (_) {
|
|
57
|
+
err.message = `Rate limit exceeded for provider "gemini": ${body.substring(0, 200)}`;
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const text = await response.text();
|
|
64
|
+
throw new Error(`Gemini API error ${response.status}: ${text.substring(0, 500)}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = await response.text();
|
|
69
|
+
throw new Error(`Gemini API error ${response.status}: ${text}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
|
|
74
|
+
// Extract content from Gemini response format
|
|
75
|
+
let content = '';
|
|
76
|
+
if (data.candidates && data.candidates[0] && data.candidates[0].content) {
|
|
77
|
+
const parts = data.candidates[0].content.parts || [];
|
|
78
|
+
content = parts.map((p) => p.text || '').join('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract usage metadata
|
|
82
|
+
const usage = data.usageMetadata || {};
|
|
83
|
+
const tokensUsed = {
|
|
84
|
+
input: usage.promptTokenCount || 0,
|
|
85
|
+
output: usage.candidatesTokenCount || 0,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return { content, tokensUsed };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
estimateCost() { return 0; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { GeminiProvider };
|
|
@@ -7,7 +7,7 @@ const DEFAULT_MODEL = 'llama-3.3-70b-versatile';
|
|
|
7
7
|
|
|
8
8
|
class GroqProvider {
|
|
9
9
|
constructor(config) {
|
|
10
|
-
this.apiKey =
|
|
10
|
+
this.apiKey = process.env.GROQ_API_KEY || config.groqApiKey;
|
|
11
11
|
if (!this.apiKey) throw new NoApiKeyError('groq');
|
|
12
12
|
this.model = config.model || DEFAULT_MODEL;
|
|
13
13
|
this.name = 'groq';
|
|
@@ -39,7 +39,9 @@ class GroqProvider {
|
|
|
39
39
|
|
|
40
40
|
if (response.status === 429) {
|
|
41
41
|
const { RateLimitError } = require('../../utils/errors');
|
|
42
|
-
const
|
|
42
|
+
const headerVal = parseInt(response.headers.get('retry-after') || '0');
|
|
43
|
+
// Minimum 15s wait — Groq free tier needs breathing room
|
|
44
|
+
const retryAfter = Math.max(headerVal * 1000, 15000);
|
|
43
45
|
throw new RateLimitError('groq', retryAfter);
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -7,7 +7,7 @@ const DEFAULT_MODEL = 'meta-llama/llama-3.3-70b-instruct:free';
|
|
|
7
7
|
|
|
8
8
|
class OpenRouterProvider {
|
|
9
9
|
constructor(config) {
|
|
10
|
-
this.apiKey =
|
|
10
|
+
this.apiKey = process.env.OPENROUTER_API_KEY || config.apiKey;
|
|
11
11
|
if (!this.apiKey) {
|
|
12
12
|
throw new NoApiKeyError('openrouter');
|
|
13
13
|
}
|
package/src/llm/queue.js
CHANGED
|
@@ -21,7 +21,18 @@ async function createQueue(chunks, provider, config, callbacks = {}) {
|
|
|
21
21
|
limit(async () => {
|
|
22
22
|
try {
|
|
23
23
|
const result = await pRetry(
|
|
24
|
-
() =>
|
|
24
|
+
async () => {
|
|
25
|
+
try {
|
|
26
|
+
return await provider.complete(chunk);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// If it's a rate-limit error, wait for the retry-after period
|
|
29
|
+
// before letting p-retry schedule the next attempt.
|
|
30
|
+
if (err.code === 'RATE_LIMIT' && err.retryAfter) {
|
|
31
|
+
await new Promise((r) => setTimeout(r, err.retryAfter));
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
25
36
|
{
|
|
26
37
|
retries: 3,
|
|
27
38
|
minTimeout: config.isFreeModel ? 8000 : 1000,
|
package/src/utils/config.js
CHANGED
|
@@ -26,7 +26,7 @@ function loadConfig(cliFlags = {}) {
|
|
|
26
26
|
try {
|
|
27
27
|
const result = explorer.search();
|
|
28
28
|
if (result && result.config) {
|
|
29
|
-
fileConfig = result.config;
|
|
29
|
+
fileConfig = { ...result.config }; // shallow copy — never mutate the cached object
|
|
30
30
|
}
|
|
31
31
|
} catch (e) {
|
|
32
32
|
// no config file found — use defaults
|
|
@@ -50,6 +50,14 @@ function loadConfig(cliFlags = {}) {
|
|
|
50
50
|
Object.entries(cliFlags).filter(([, v]) => v !== undefined)
|
|
51
51
|
);
|
|
52
52
|
|
|
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;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
return { ...defaults, ...fileConfig, ...cleanCli };
|
|
54
62
|
}
|
|
55
63
|
|