scai 0.1.84 → 0.1.86
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/README.md +452 -0
- package/dist/CHANGELOG.md +9 -1
- package/dist/agentManager.js +46 -0
- package/dist/db/backup.js +32 -20
- package/dist/index.js +13 -7
- package/dist/pipeline/modules/cleanupModule.js +79 -31
- package/dist/pipeline/modules/commentModule.js +24 -10
- package/dist/pipeline/modules/commitSuggesterModule.js +10 -4
- package/dist/pipeline/modules/generateTestsModule.js +1 -2
- package/dist/pipeline/modules/summaryModule.js +0 -1
- package/dist/pipeline/registry/moduleRegistry.js +5 -1
- package/dist/pipeline/runModulePipeline.js +11 -11
- package/dist/utils/splitCodeIntoChunk.js +38 -0
- package/package.json +2 -1
- package/dist/commands/RefactorCmd.js +0 -35
- package/dist/commands/TestGenCmd.js +0 -14
package/README.md
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# ⚙️ scai — Smart Commit AI ✨
|
|
2
|
+
|
|
3
|
+
> AI-powered CLI tool for commit messages **and** pull request reviews — using local models.
|
|
4
|
+
|
|
5
|
+
**scai** is your AI pair‑programmer in the terminal. Focus on coding while scai:
|
|
6
|
+
|
|
7
|
+
- 🤖 **Reviews open pull requests** and provides AI‑driven feedback (BETA)
|
|
8
|
+
- 💬 **Suggests intelligent Git commit messages** based on your staged diff
|
|
9
|
+
- 📝 Summarizes files in plain English
|
|
10
|
+
- 📜 Auto‑updates your changelog
|
|
11
|
+
- 🔍 (ALPHA) Search & ask questions across your codebase
|
|
12
|
+
- 🔐 100% local — no API keys, no cloud, no telemetry
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🚀 Features
|
|
17
|
+
|
|
18
|
+
- ⚡ Powered by open-source models (e.g. `llama3`, `codellama`)
|
|
19
|
+
- 🔍 Full-text indexing & semantic search (ALPHA)
|
|
20
|
+
- 🛠️ Built with Node.js and TypeScript
|
|
21
|
+
- ✅ Easily configurable via CLI or global flags
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ❤️ Why Local AI?
|
|
26
|
+
|
|
27
|
+
**Your code stays yours.**
|
|
28
|
+
scai runs entirely on your machine and doesn't require cloud APIs or API keys. That means:
|
|
29
|
+
|
|
30
|
+
- ✅ **Privacy-first**: no telemetry, no server round-trips
|
|
31
|
+
- ✅ **EU & GDPR-friendly**: designed with compliance in mind
|
|
32
|
+
- ✅ **Developer control**: full transparency and override options
|
|
33
|
+
- ✅ **Offline support**: works even without an internet connection
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
|
|
39
|
+
1. **Install Ollama (for local models)**
|
|
40
|
+
- macOS: `brew install ollama`
|
|
41
|
+
- Windows: [Download here](https://ollama.com/download)
|
|
42
|
+
- Start Ollama after installing.
|
|
43
|
+
|
|
44
|
+
2. **Install scai globally:**
|
|
45
|
+
```bash
|
|
46
|
+
npm install -g scai
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. **Initialize models:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
scai init
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## ✨ AI Code Review, Powered by Your Terminal
|
|
56
|
+
|
|
57
|
+
No more struggling to write pull request descriptions by hand. `scai git review` automatically generates a rich summary of your changes, complete with context, suggestions, and rationale.
|
|
58
|
+
|
|
59
|
+
> ⚠️ These features are in **beta** — feedback welcome!
|
|
60
|
+
Ping [@ticcr](https://bsky.app/profile/ticcr.xyz) on Bluesky — I'd love to hear your thoughts!
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### 🔑 Setting Up Authentication (Required)
|
|
65
|
+
|
|
66
|
+
To interact with GitHub and create pull requests, `scai` needs a personal access token with **repo** permissions.
|
|
67
|
+
|
|
68
|
+
1. **Create your GitHub Access Token**
|
|
69
|
+
Follow this link to generate a token: [https://github.com/settings/personal-access-tokens](https://github.com/settings/personal-access-tokens)
|
|
70
|
+
|
|
71
|
+
Make sure you enable at least:
|
|
72
|
+
|
|
73
|
+
* `repo` (Full control of private repositories)
|
|
74
|
+
* `workflow` (If you want PRs to trigger CI)
|
|
75
|
+
|
|
76
|
+
2. **Set the token in scai:**
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
scai auth set
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This stores your token locally in a secure config file. You can inspect the setup at any time:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
scai auth check
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
3. **Set the index dir:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
scai index set /path/to/repo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This is the repo from which scai will look up pull requests that can be reviewed.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
## ⚒️ Usage Overview
|
|
98
|
+
### 🧠 How to Use `scai git review`
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
scai git review
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This will show you pull requests assigned to you for review:
|
|
105
|
+
|
|
106
|
+
* Understand the diffs using a local model
|
|
107
|
+
* Generate a structured pull request:
|
|
108
|
+
|
|
109
|
+
* ✅ Title
|
|
110
|
+
* ✅ Summary of changes
|
|
111
|
+
* ✅ Explanation of why the changes matter
|
|
112
|
+
* ✅ Optional changelog bullets
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
SCAI supports an integrated review flow for GitHub pull requests. To get started:
|
|
116
|
+
|
|
117
|
+
1. **Set your working index directory (once per repo):**
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
scai index set /path/to/repo
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
2. **Authenticate with GitHub:**
|
|
124
|
+
```sh
|
|
125
|
+
scai git review
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This command will query you for the Personal Access Token and set it for you.
|
|
129
|
+
You may also do this with the auth commands below
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
scai auth set
|
|
133
|
+
scai auth check
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
3. **Fetch and review pull requests:**
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
scai git review
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use `-a` to list all PRs that require a review:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
scai git review -a
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Example Workflow
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
$ scai git review -a
|
|
152
|
+
📦 Resolving GitHub repo info from indexDir: ./
|
|
153
|
+
🔗 Git origin URL: git@github.com:org/repo.git
|
|
154
|
+
✅ Parsed: owner='org', repo='repo'
|
|
155
|
+
👤 Authenticated user: dev-user123
|
|
156
|
+
|
|
157
|
+
🔍 Fetching pull requests and diffs...
|
|
158
|
+
✅ Fetched 5 PR(s) with diffs.
|
|
159
|
+
|
|
160
|
+
📦 Open Pull Requests with review requested:
|
|
161
|
+
| # | ID | TITLE | AUTHOR | STATUS | CREATED | REVIEWERS | REVIEWS |
|
|
162
|
+
| - | ---- | ----------------------------- | ---------- | ------ | ---------- | ----------------------------- | ------------------- |
|
|
163
|
+
| 1 | #120 | fix/session-timeout | dev-alice | Open | 2025-08-08 | code-analyzer\[bot], dev-bob | ✅ Approved |
|
|
164
|
+
| 2 | #118 | feature/1482-support-wfs2 | dev-carol | Open | 2025-08-07 | code-analyzer\[bot], dev-dave | ✅ Approved |
|
|
165
|
+
| 3 | #117 | refactor/win-server-support | dev-erin | Open | 2025-08-06 | dev-frank, dev-alice | ❌ Changes Requested |
|
|
166
|
+
| 4 | #114 | bump/vue-i18n-9.14.5 | dependabot | Open | 2025-08-04 | code-analyzer\[bot] | ✅ Approved |
|
|
167
|
+
| 5 | #113 | bugfix/null-navigator-check | dev-bob | Open | 2025-08-03 | dev-alice, dev-carol | ✅ Approved |
|
|
168
|
+
|
|
169
|
+
👉 Choose a PR to review [1-2]: 1
|
|
170
|
+
✅ Model response received.
|
|
171
|
+
|
|
172
|
+
💡 AI-suggested review:
|
|
173
|
+
|
|
174
|
+
Solid improvement — this patch improves session stability and handles async state more reliably.
|
|
175
|
+
You might consider renaming `sessionManager` to better reflect its dual role in auth and persistence.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
1) ✅ Approve
|
|
179
|
+
2) ❌ Reject
|
|
180
|
+
3) ✍️ Edit
|
|
181
|
+
4) Write your own review
|
|
182
|
+
5) 🚪 Cancel
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
### 🔧 How to Use `scai git commit`
|
|
188
|
+
|
|
189
|
+
Use AI to suggest a meaningful commit message based on your staged code:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git add .
|
|
193
|
+
scai git commit
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
You can also include a changelog entry along with the commit:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
scai git commit --changelog
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This will:
|
|
203
|
+
1. Suggest a commit message based on your `git diff --cached`
|
|
204
|
+
2. Propose a changelog entry (if relevant)
|
|
205
|
+
3. Allow you to approve, regenerate, or skip the changelog
|
|
206
|
+
4. Automatically stage and commit the changes
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### 📝 Generate a Standalone Changelog Entry
|
|
211
|
+
|
|
212
|
+
If you want to generate a changelog entry without committing:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
scai gen changelog
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This will:
|
|
219
|
+
- Analyze the current `git diff` (staged or unstaged)
|
|
220
|
+
- Propose a list of **user-facing changes** in clean markdown bullet points
|
|
221
|
+
- Let you accept, regenerate, or skip the update
|
|
222
|
+
- Append the entry to `CHANGELOG.md` and stage it if accepted
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
### 🛠️ Code Generation Commands (`gen` group)
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
scai gen summ <file>
|
|
229
|
+
scai gen comm <file>
|
|
230
|
+
scai gen changelog
|
|
231
|
+
scai gen tests <file>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
* `summ`: Summarize a file
|
|
235
|
+
* `comm`: Add comments to a file
|
|
236
|
+
* `changelog`: Update or create `CHANGELOG.md` from Git diff
|
|
237
|
+
* `tests`: Create Jest test stubs (ALPHA)
|
|
238
|
+
|
|
239
|
+
You can also pipe file content directly:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
cat src/utils/math.ts | scai gen summ
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## ⚙️ Configuration
|
|
248
|
+
|
|
249
|
+
scai stores settings in `~/.scai/config.json`. You can override or view them:
|
|
250
|
+
|
|
251
|
+
* **Set model:**
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
scai set model codellama:7b
|
|
255
|
+
```
|
|
256
|
+
* **Set language:**
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
scai set lang ts
|
|
260
|
+
```
|
|
261
|
+
* **Show config:**
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
scai config
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
<br>
|
|
268
|
+
|
|
269
|
+
## 🔁 Background Daemon and Indexing ⚠️ ALPHA Notice
|
|
270
|
+
|
|
271
|
+
These features are experimental and subject to change:
|
|
272
|
+
|
|
273
|
+
* `index`, `find`, `ask`
|
|
274
|
+
* `daemon`, `stop-daemon`
|
|
275
|
+
* `gen tests` (test generation)
|
|
276
|
+
|
|
277
|
+
<br>
|
|
278
|
+
|
|
279
|
+
## Commands
|
|
280
|
+
|
|
281
|
+
### `index`
|
|
282
|
+
The `index` command is used to manage and perform operations on the indexed files and repositories.
|
|
283
|
+
|
|
284
|
+
#### `scai index start`
|
|
285
|
+
|
|
286
|
+
Index supported files in the configured index directory.
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
scai index set <dir>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Set and activate the index directory.
|
|
293
|
+
```bash
|
|
294
|
+
scai index list
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
List all indexed repositories.
|
|
298
|
+
```bash
|
|
299
|
+
scai index switch
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Switch active repository (by key or indexDir). Run without input for an interactive list of repositories.
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
> **Note:** Indexing very large repositories (millions of lines) may take **hours or days**. Please be patient, and only index huge codebases if you are ok with some extra processing taking place on your computer.
|
|
308
|
+
|
|
309
|
+
The `scai index` command **automatically** starts a background daemon that continuously:
|
|
310
|
+
|
|
311
|
+
* Scans your target directory
|
|
312
|
+
* Summarizes new or changed files
|
|
313
|
+
* Updates embeddings and the search index
|
|
314
|
+
|
|
315
|
+
You won't gain much value from the index unless you scope it to one repository.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### 🔍 Codebase Search & Ask (ALPHA)
|
|
320
|
+
|
|
321
|
+
> **Important:** You must `index` a **code repository** first or `find` and `ask` have no context to work with.
|
|
322
|
+
|
|
323
|
+
1. **Set index directory:**
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
scai index set /path/to/repo
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
2. **Index your repo (once):**
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
scai index start
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
3. The daemon is designed to **consume minimal resources** and run unobtrusively. You can control it with:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
scai daemon # Start or show daemon status
|
|
339
|
+
scai stop-daemon # Stop the background indexer
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
4. **Keyword search:**
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
scai find YourClassName
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
5. **Natural-language questions:**
|
|
350
|
+
|
|
351
|
+
### 🧠 Natural-language questions
|
|
352
|
+
|
|
353
|
+
Ask questions about your codebase using `scai ask`.
|
|
354
|
+
|
|
355
|
+
You can run it in two ways:
|
|
356
|
+
|
|
357
|
+
1. **Inline question**
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
scai ask "How does the controller work?"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
2. **Interactive prompt**
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
scai ask
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Press enter**
|
|
370
|
+
, then type your question when prompted:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
> How does the controller work?
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
</br>
|
|
377
|
+
|
|
378
|
+
### 🚨 **OBS** 🚨
|
|
379
|
+
|
|
380
|
+
`find` and `ask` rely on that index—without it they will return no useful results.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
Note the **Migrate** command is for **internal use only**. Do **not** run it unless explicitly instructed, as it may delete existing data or corrupt your local database.
|
|
385
|
+
|
|
386
|
+
If you're upgrading from an earlier version, please run the following commands to avoid indexing issues:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
scai reset-db
|
|
390
|
+
scai index
|
|
391
|
+
```
|
|
392
|
+
</br>
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 🛍️ Maintenance & Utilities
|
|
397
|
+
|
|
398
|
+
* **Reset database (w/ backup):**
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
scai reset-db
|
|
402
|
+
```
|
|
403
|
+
* **Backup only of `~/.scai`:**
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
scai backup
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 🧺 Module Pipeline Mode
|
|
412
|
+
|
|
413
|
+
For custom pipelines on a single file:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
scai src/file.ts -m summary,comments
|
|
417
|
+
```
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## 🔐 License & Fair Use
|
|
421
|
+
|
|
422
|
+
**scai is free to use** for individuals, teams, and commercial projects.
|
|
423
|
+
|
|
424
|
+
You may:
|
|
425
|
+
|
|
426
|
+
* ✅ Use internally or commercially
|
|
427
|
+
* ✅ Fork and improve
|
|
428
|
+
* ✅ Recommend to others
|
|
429
|
+
|
|
430
|
+
You may **not**:
|
|
431
|
+
|
|
432
|
+
* ❌ Resell as a product or service
|
|
433
|
+
* ❌ Claim ownership of the tool
|
|
434
|
+
|
|
435
|
+
</br>
|
|
436
|
+
|
|
437
|
+
### 📄 License
|
|
438
|
+
|
|
439
|
+
Free for personal and internal company use only.
|
|
440
|
+
Commercial use (resale, SaaS, inclusion in paid tools) is prohibited.
|
|
441
|
+
Contact me for commercial licensing.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
</br>
|
|
446
|
+
|
|
447
|
+
## 🙌 Feedback
|
|
448
|
+
|
|
449
|
+
Questions, ideas, or bugs?
|
|
450
|
+
Ping [@ticcr](https://bsky.app/profile/ticcr.xyz) on Bluesky — I'd love to hear your thoughts!
|
|
451
|
+
|
|
452
|
+
---
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -99,4 +99,12 @@ Type handling with the module pipeline
|
|
|
99
99
|
• Update .gitignore to ignore Node and build artifacts, editor/IDE files, and project-specific ignores.
|
|
100
100
|
• Added scripts for building cli, server, and dashboard
|
|
101
101
|
• Restructure the workspace into 3 modules, cli, dashboard, server
|
|
102
|
-
• Improved `askChangelogApproval` to support editing and skipping of changelog entries
|
|
102
|
+
• Improved `askChangelogApproval` to support editing and skipping of changelog entries
|
|
103
|
+
|
|
104
|
+
## 2025-08-12
|
|
105
|
+
|
|
106
|
+
* Added support for backing up SCAI folder with timestamped directory.
|
|
107
|
+
* Split file processing into logical chunks with logging.
|
|
108
|
+
* Improved commit suggestion generation logic.
|
|
109
|
+
* Removed unnecessary files and updated index.ts to use handleAgentRun instead.
|
|
110
|
+
* Fetch download statistics from NPM API.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { runModulePipeline } from './pipeline/runModulePipeline.js';
|
|
4
|
+
import { normalizePath } from './utils/normalizePath.js';
|
|
5
|
+
import { readConfig } from './config.js';
|
|
6
|
+
import { countTokens, splitCodeIntoChunks } from './utils/splitCodeIntoChunk.js';
|
|
7
|
+
export async function handleAgentRun(filepath, modules) {
|
|
8
|
+
try {
|
|
9
|
+
filepath = normalizePath(filepath);
|
|
10
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
11
|
+
const totalTokens = countTokens(content);
|
|
12
|
+
console.log(chalk.blue(`🧮 Total tokens in file:`), chalk.yellow(totalTokens.toString()));
|
|
13
|
+
const config = readConfig();
|
|
14
|
+
const maxTokens = 1500;
|
|
15
|
+
const chunks = splitCodeIntoChunks(content, maxTokens);
|
|
16
|
+
console.log(chalk.magenta(`📦 Split into ${chunks.length} chunks`));
|
|
17
|
+
const processedChunks = [];
|
|
18
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
19
|
+
const chunkTokens = countTokens(chunk);
|
|
20
|
+
if (i === 0) {
|
|
21
|
+
console.log(chalk.cyan(`🔍 Processing ${chunks.length} chunks of file:`), chalk.white(filepath));
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk.gray(` - Chunk ${i + 1} tokens:`), chalk.yellow(chunkTokens.toString()));
|
|
24
|
+
const chunkInput = {
|
|
25
|
+
content: chunk,
|
|
26
|
+
filepath,
|
|
27
|
+
chunkIndex: i,
|
|
28
|
+
chunkCount: chunks.length,
|
|
29
|
+
};
|
|
30
|
+
const response = await runModulePipeline(modules, chunkInput);
|
|
31
|
+
if (!response.content.trim()) {
|
|
32
|
+
throw new Error(`⚠️ Model returned empty result on chunk ${i + 1}`);
|
|
33
|
+
}
|
|
34
|
+
processedChunks.push(response.content);
|
|
35
|
+
console.log(chalk.green(`✅ Finished chunk ${i + 1}/${chunks.length}`));
|
|
36
|
+
}
|
|
37
|
+
// Join all chunk outputs into one string
|
|
38
|
+
const finalOutput = processedChunks.join('\n\n');
|
|
39
|
+
// Overwrite original file here:
|
|
40
|
+
await fs.writeFile(filepath, finalOutput, 'utf-8');
|
|
41
|
+
console.log(chalk.green(`✅ Original file overwritten: ${filepath}`));
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.error(chalk.red('❌ Error in agent run:'), err instanceof Error ? err.message : err);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/db/backup.js
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
// src/db/backup.ts
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
function getBackupDir() {
|
|
5
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
6
|
-
return path.join(SCAI_HOME, `backup-${timestamp}`);
|
|
7
|
-
}
|
|
4
|
+
import os from 'os';
|
|
8
5
|
export async function backupScaiFolder() {
|
|
9
|
-
const backupDir = getBackupDir();
|
|
10
6
|
try {
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (stat.isFile()) {
|
|
18
|
-
await fsp.copyFile(srcPath, destPath);
|
|
19
|
-
}
|
|
7
|
+
const homeDir = os.homedir();
|
|
8
|
+
const scaiDir = path.join(homeDir, '.scai');
|
|
9
|
+
// Ensure the .scai folder exists
|
|
10
|
+
if (!fs.existsSync(scaiDir)) {
|
|
11
|
+
console.error(`⚠️ No .scai folder found at ${scaiDir}`);
|
|
12
|
+
return false;
|
|
20
13
|
}
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
// Backup path: ~/.scai_backup_<timestamp>
|
|
15
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
16
|
+
const backupDir = path.join(homeDir, `.scai_backup_${timestamp}`);
|
|
17
|
+
// Recursive copy
|
|
18
|
+
copyRecursiveSync(scaiDir, backupDir);
|
|
19
|
+
return true;
|
|
23
20
|
}
|
|
24
21
|
catch (err) {
|
|
25
|
-
console.
|
|
26
|
-
return
|
|
22
|
+
console.error(`⚠️ Failed to back up .scai folder:`, err);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function copyRecursiveSync(src, dest) {
|
|
27
|
+
const stats = fs.statSync(src);
|
|
28
|
+
if (stats.isDirectory()) {
|
|
29
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
30
|
+
const entries = fs.readdirSync(src);
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const srcPath = path.join(src, entry);
|
|
33
|
+
const destPath = path.join(dest, entry);
|
|
34
|
+
copyRecursiveSync(srcPath, destPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
fs.copyFileSync(src, dest);
|
|
27
39
|
}
|
|
28
40
|
}
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,6 @@ const { version } = require('../package.json');
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { Config } from './config.js';
|
|
7
7
|
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
8
|
-
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
9
|
-
import { generateTests } from "./commands/TestGenCmd.js";
|
|
10
8
|
import { bootstrap } from './modelSetup.js';
|
|
11
9
|
import { summarizeFile } from "./commands/SummaryCmd.js";
|
|
12
10
|
import { handleStandaloneChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
|
|
@@ -28,6 +26,10 @@ import { runInteractiveSwitch } from "./commands/SwitchCmd.js";
|
|
|
28
26
|
import { execSync } from "child_process";
|
|
29
27
|
import { fileURLToPath } from "url";
|
|
30
28
|
import { dirname, resolve } from "path";
|
|
29
|
+
import { handleAgentRun } from './agentManager.js';
|
|
30
|
+
import { addCommentsModule } from './pipeline/modules/commentModule.js';
|
|
31
|
+
import { generateTestsModule } from './pipeline/modules/generateTestsModule.js';
|
|
32
|
+
import { cleanupModule } from './pipeline/modules/cleanupModule.js';
|
|
31
33
|
// 🎛️ CLI Setup
|
|
32
34
|
const cmd = new Command('scai')
|
|
33
35
|
.version(version)
|
|
@@ -103,8 +105,9 @@ const gen = cmd.command('gen').description('Generate code-related output');
|
|
|
103
105
|
gen
|
|
104
106
|
.command('comm <file>')
|
|
105
107
|
.description('Write comments for the given file')
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
+
.action((file) => {
|
|
109
|
+
handleAgentRun(file, [addCommentsModule, cleanupModule]);
|
|
110
|
+
});
|
|
108
111
|
gen
|
|
109
112
|
.command('changelog')
|
|
110
113
|
.description('Update or create the CHANGELOG.md based on current Git diff')
|
|
@@ -116,9 +119,12 @@ gen
|
|
|
116
119
|
.description('Print a summary of the given file to the terminal')
|
|
117
120
|
.action((file) => summarizeFile(file));
|
|
118
121
|
gen
|
|
119
|
-
.command('
|
|
120
|
-
.description('Generate
|
|
121
|
-
.
|
|
122
|
+
.command('testgen <file>')
|
|
123
|
+
.description('Generate tests for the given file')
|
|
124
|
+
.option('-a, --apply', 'Apply the output to the original file')
|
|
125
|
+
.action((file) => {
|
|
126
|
+
handleAgentRun(file, [generateTestsModule]);
|
|
127
|
+
});
|
|
122
128
|
// ⚙️ Group: Configuration settings
|
|
123
129
|
const config = cmd.command('config').description('Manage SCAI configuration');
|
|
124
130
|
config
|
|
@@ -1,42 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
function isTopOrBottomNoise(line) {
|
|
3
|
+
const trimmed = line.trim();
|
|
4
|
+
if (/^```(?:\w+)?$/.test(trimmed))
|
|
5
|
+
return true;
|
|
6
|
+
if (/^<!--.*-->$/.test(trimmed))
|
|
7
|
+
return true;
|
|
8
|
+
const lower = trimmed.toLowerCase();
|
|
9
|
+
if (!trimmed.startsWith('//') && !trimmed.startsWith('/*')) {
|
|
10
|
+
return [
|
|
11
|
+
/^i\s/i,
|
|
12
|
+
/^here/,
|
|
13
|
+
/^this/,
|
|
14
|
+
/^the following/,
|
|
15
|
+
/^below/,
|
|
16
|
+
/^in this/,
|
|
17
|
+
/^we have/,
|
|
18
|
+
/the code above/,
|
|
19
|
+
/ensures that/,
|
|
20
|
+
/it handles/,
|
|
21
|
+
/used to/,
|
|
22
|
+
/note that/,
|
|
23
|
+
/example/,
|
|
24
|
+
/summary/,
|
|
25
|
+
/added comments/,
|
|
26
|
+
].some(pattern => pattern.test(lower));
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
14
29
|
}
|
|
15
30
|
export const cleanupModule = {
|
|
16
31
|
name: 'cleanup',
|
|
17
|
-
description: 'Remove markdown fences and
|
|
18
|
-
async run(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
description: 'Remove markdown fences and fluff from top/bottom of each chunk with colored logging',
|
|
33
|
+
async run(input) {
|
|
34
|
+
// Normalize line endings to \n to avoid issues with \r\n
|
|
35
|
+
let content = input.content.replace(/\r\n/g, '\n');
|
|
36
|
+
let lines = content.split('\n');
|
|
37
|
+
// --- CLEAN TOP ---
|
|
38
|
+
// Remove noise lines before the first triple tick or end
|
|
39
|
+
while (lines.length && (lines[0].trim() === '' || isTopOrBottomNoise(lines[0]))) {
|
|
40
|
+
if (/^```(?:\w+)?$/.test(lines[0].trim()))
|
|
41
|
+
break; // Stop if opening fence found
|
|
42
|
+
console.log(chalk.red(`[cleanupModule] Removing noise from top:`), chalk.yellow(`"${lines[0].trim()}"`));
|
|
43
|
+
lines.shift();
|
|
29
44
|
}
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
// If opening fence found at top, find matching closing fence
|
|
46
|
+
if (lines.length && /^```(?:\w+)?$/.test(lines[0].trim())) {
|
|
47
|
+
console.log(chalk.red(`[cleanupModule] Found opening fenced block at top.`));
|
|
48
|
+
// Remove opening fence line
|
|
49
|
+
lines.shift();
|
|
50
|
+
// Find closing fence index
|
|
51
|
+
let closingIndex = -1;
|
|
52
|
+
for (let i = 0; i < lines.length; i++) {
|
|
53
|
+
if (/^```(?:\w+)?$/.test(lines[i].trim())) {
|
|
54
|
+
closingIndex = i;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (closingIndex !== -1) {
|
|
59
|
+
console.log(chalk.red(`[cleanupModule] Found closing fenced block at line ${closingIndex + 1}, removing fence lines.`));
|
|
60
|
+
// Remove closing fence line
|
|
61
|
+
lines.splice(closingIndex, 1);
|
|
35
62
|
}
|
|
36
63
|
else {
|
|
37
|
-
|
|
64
|
+
console.log(chalk.yellow(`[cleanupModule] No closing fenced block found, only removed opening fence.`));
|
|
38
65
|
}
|
|
66
|
+
// NO removal of noise lines after fenced block here (to keep new comments intact)
|
|
67
|
+
}
|
|
68
|
+
// --- CLEAN BOTTOM ---
|
|
69
|
+
// If closing fence found at bottom, remove only that triple tick line
|
|
70
|
+
if (lines.length && /^```(?:\w+)?$/.test(lines[lines.length - 1].trim())) {
|
|
71
|
+
console.log(chalk.red(`[cleanupModule] Removing closing fenced block line at bottom.`));
|
|
72
|
+
lines.pop();
|
|
39
73
|
}
|
|
74
|
+
// Remove noise lines after closing fence (now bottom)
|
|
75
|
+
while (lines.length && (lines[lines.length - 1].trim() === '' || isTopOrBottomNoise(lines[lines.length - 1]))) {
|
|
76
|
+
console.log(chalk.red(`[cleanupModule] Removing noise from bottom after fenced block:`), chalk.yellow(`"${lines[lines.length - 1].trim()}"`));
|
|
77
|
+
lines.pop();
|
|
78
|
+
}
|
|
79
|
+
// --- FINAL CLEANUP: REMOVE ANY LINGERING TRIPLE TICK LINES ANYWHERE ---
|
|
80
|
+
lines = lines.filter(line => {
|
|
81
|
+
const trimmed = line.trim();
|
|
82
|
+
if (/^```(?:\w+)?$/.test(trimmed)) {
|
|
83
|
+
console.log(chalk.red(`[cleanupModule] Removing lingering triple tick line:`), chalk.yellow(`"${line}"`));
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
40
88
|
return { content: lines.join('\n').trim() };
|
|
41
89
|
}
|
|
42
90
|
};
|
|
@@ -2,7 +2,7 @@ import { Config } from '../../config.js';
|
|
|
2
2
|
import { generate } from '../../lib/generate.js';
|
|
3
3
|
import { detectFileType } from '../../fileRules/detectFileType.js';
|
|
4
4
|
export const addCommentsModule = {
|
|
5
|
-
name: '
|
|
5
|
+
name: 'comments',
|
|
6
6
|
description: 'Adds meaningful comments to any file type (code, config, or data)',
|
|
7
7
|
async run(input) {
|
|
8
8
|
const model = Config.getModel();
|
|
@@ -37,23 +37,37 @@ export const addCommentsModule = {
|
|
|
37
37
|
text: '#',
|
|
38
38
|
};
|
|
39
39
|
const commentSyntax = commentMap[fileType] || '//';
|
|
40
|
+
const chunkLabel = (input.chunkIndex && input.chunkCount && input.chunkCount > 1)
|
|
41
|
+
? `CHUNK ${input.chunkIndex + 1}/${input.chunkCount} of ${input.filepath}`
|
|
42
|
+
: `FULL FILE: ${input.filepath}`;
|
|
40
43
|
const prompt = `
|
|
41
44
|
You are a senior engineer reviewing a ${fileType} file.
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
Please:
|
|
47
|
+
|
|
48
|
+
1. Add a concise summary comment above each function describing its purpose.
|
|
49
|
+
- Do NOT write trivial summaries like "..." or empty comments.
|
|
50
|
+
- Do NOT overwrite or remove any existing comments.
|
|
51
|
+
|
|
52
|
+
2. Add clear, helpful inline comments to explain non-obvious logic inside functions.
|
|
53
|
+
- Preserve all existing comments as-is.
|
|
54
|
+
|
|
55
|
+
3. Use "${commentSyntax}" as the comment syntax appropriate for ${fileType}.
|
|
56
|
+
|
|
57
|
+
4. Preserve all original formatting, whitespace, and code exactly.
|
|
45
58
|
|
|
46
59
|
Rules:
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
60
|
+
- Return the full original chunk of code with added comments only.
|
|
61
|
+
- Do NOT remove, change, or overwrite any existing code or comments.
|
|
62
|
+
- Summaries should be brief but meaningful.
|
|
63
|
+
- Inline comments should clarify complex or tricky parts only.
|
|
51
64
|
|
|
52
|
-
---
|
|
65
|
+
--- ${chunkLabel} START ---
|
|
53
66
|
${input.content}
|
|
54
|
-
---
|
|
67
|
+
--- ${chunkLabel} END ---
|
|
55
68
|
`.trim();
|
|
56
69
|
const response = await generate({ content: prompt }, model);
|
|
57
|
-
|
|
70
|
+
const contentToReturn = (response.content && response.content !== 'NO UPDATE') ? response.content : input.content;
|
|
71
|
+
return { content: contentToReturn };
|
|
58
72
|
},
|
|
59
73
|
};
|
|
@@ -7,11 +7,17 @@ export const commitSuggesterModule = {
|
|
|
7
7
|
const model = Config.getModel();
|
|
8
8
|
const prompt = `
|
|
9
9
|
Suggest ALWAYS 3 concise, conventional Git commit messages based on the input code diff.
|
|
10
|
-
Use this format ONLY:
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
- Follow the Conventional Commits specification for prefixes (e.g., feat:, fix:, refactor:, docs:, style:, test:, chore:).
|
|
12
|
+
- Choose the most appropriate prefix for each message based on the changes in the diff.
|
|
13
|
+
- Do NOT repeat the same prefix unless it is clearly the most appropriate.
|
|
14
|
+
- Keep each message short, clear, and action-oriented.
|
|
15
|
+
|
|
16
|
+
Format your response exactly as:
|
|
17
|
+
|
|
18
|
+
1. <type>: <message>
|
|
19
|
+
2. <type>: <message>
|
|
20
|
+
3. <type>: <message>
|
|
15
21
|
|
|
16
22
|
Here is the diff:
|
|
17
23
|
${content}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// src/pipeline/modules/generateTestsModule.ts
|
|
2
1
|
import fs from 'fs/promises';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import { Config } from '../../config.js';
|
|
5
4
|
import { generate } from '../../lib/generate.js';
|
|
6
5
|
export const generateTestsModule = {
|
|
7
|
-
name: '
|
|
6
|
+
name: 'tests',
|
|
8
7
|
description: 'Generate a Jest test file for the class/module',
|
|
9
8
|
async run({ content, filepath }) {
|
|
10
9
|
const model = Config.getModel();
|
|
@@ -8,7 +8,6 @@ export const summaryModule = {
|
|
|
8
8
|
const model = Config.getModel();
|
|
9
9
|
const ext = filepath ? path.extname(filepath).toLowerCase() : '';
|
|
10
10
|
const filename = filepath ? path.basename(filepath) : '';
|
|
11
|
-
// More neutral prompt for general-purpose content
|
|
12
11
|
const prompt = `
|
|
13
12
|
You are an assistant specialized in summarizing files.
|
|
14
13
|
|
|
@@ -16,6 +16,10 @@ const builtInModules = {
|
|
|
16
16
|
export function getModuleByName(name) {
|
|
17
17
|
return builtInModules[name];
|
|
18
18
|
}
|
|
19
|
+
// Return module metadata for CLI or UI
|
|
19
20
|
export function listAvailableModules() {
|
|
20
|
-
return Object.
|
|
21
|
+
return Object.values(builtInModules).map(({ name, description }) => ({
|
|
22
|
+
name,
|
|
23
|
+
description: description || 'No description available',
|
|
24
|
+
}));
|
|
21
25
|
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
export async function runModulePipeline(modules, input) {
|
|
2
3
|
let current = input;
|
|
3
|
-
|
|
4
|
-
const isDebug = false;
|
|
5
|
-
if (isDebug) {
|
|
6
|
-
console.log('Input: ', input);
|
|
7
|
-
}
|
|
8
|
-
let response = { content: '' };
|
|
4
|
+
const isDebug = true;
|
|
9
5
|
for (const mod of modules) {
|
|
10
6
|
try {
|
|
11
|
-
response = await mod.run(current);
|
|
12
7
|
if (isDebug) {
|
|
13
|
-
console.log(
|
|
14
|
-
|
|
8
|
+
console.log(chalk.green('➡️ Input:', current.content));
|
|
9
|
+
}
|
|
10
|
+
const response = await mod.run(current);
|
|
11
|
+
console.log(`⚙️ Running: ${mod.name}`);
|
|
12
|
+
if (isDebug) {
|
|
13
|
+
console.log(chalk.red('➡️ Output:', response.content));
|
|
15
14
|
}
|
|
15
|
+
// Feed this output into the next module
|
|
16
|
+
current = response;
|
|
16
17
|
}
|
|
17
18
|
catch (error) {
|
|
18
19
|
console.error(`❌ Error in ${mod.name}:`, error instanceof Error ? error.message : error);
|
|
19
20
|
throw new Error(`Pipeline failed at module ${mod.name}`);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
return response; // Ensure the return type matches PromptOutput
|
|
23
|
+
return current;
|
|
24
24
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { encode } from 'gpt-3-encoder';
|
|
2
|
+
export function splitCodeIntoChunks(text, maxTokens) {
|
|
3
|
+
const lines = text.split('\n');
|
|
4
|
+
const chunks = [];
|
|
5
|
+
let currentChunkLines = [];
|
|
6
|
+
let currentTokens = 0;
|
|
7
|
+
for (const line of lines) {
|
|
8
|
+
const lineTokens = encode(line + '\n').length;
|
|
9
|
+
if (currentTokens + lineTokens > maxTokens) {
|
|
10
|
+
// Try to split at a more natural point
|
|
11
|
+
let splitIndex = currentChunkLines.length;
|
|
12
|
+
for (let i = currentChunkLines.length - 1; i >= 0; i--) {
|
|
13
|
+
const trimmed = currentChunkLines[i].trim();
|
|
14
|
+
if (trimmed === '' ||
|
|
15
|
+
trimmed.startsWith('function ') ||
|
|
16
|
+
trimmed.startsWith('class ') ||
|
|
17
|
+
trimmed.endsWith('}') ||
|
|
18
|
+
trimmed.endsWith(';')) {
|
|
19
|
+
splitIndex = i + 1;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
chunks.push(currentChunkLines.slice(0, splitIndex).join('\n'));
|
|
24
|
+
// Move leftover lines into the next chunk
|
|
25
|
+
currentChunkLines = currentChunkLines.slice(splitIndex);
|
|
26
|
+
currentTokens = encode(currentChunkLines.join('\n')).length;
|
|
27
|
+
}
|
|
28
|
+
currentChunkLines.push(line);
|
|
29
|
+
currentTokens += lineTokens;
|
|
30
|
+
}
|
|
31
|
+
if (currentChunkLines.length > 0) {
|
|
32
|
+
chunks.push(currentChunkLines.join('\n'));
|
|
33
|
+
}
|
|
34
|
+
return chunks;
|
|
35
|
+
}
|
|
36
|
+
export function countTokens(text) {
|
|
37
|
+
return encode(text).length;
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"scai": "./dist/index.js"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"columnify": "^1.6.0",
|
|
45
45
|
"commander": "^11.0.0",
|
|
46
46
|
"fast-glob": "^3.3.3",
|
|
47
|
+
"gpt-3-encoder": "^1.1.4",
|
|
47
48
|
"proper-lockfile": "^4.1.2",
|
|
48
49
|
"string-similarity-js": "^2.1.4",
|
|
49
50
|
"ts-morph": "^26.0.0"
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { runModulePipeline } from '../pipeline/runModulePipeline.js';
|
|
4
|
-
import { addCommentsModule } from '../pipeline/modules/commentModule.js';
|
|
5
|
-
import { normalizePath } from '../utils/normalizePath.js';
|
|
6
|
-
export async function handleRefactor(filepath, options = {}) {
|
|
7
|
-
try {
|
|
8
|
-
// Normalize and resolve filepath (includes expanding ~ and consistent slashes)
|
|
9
|
-
filepath = normalizePath(filepath);
|
|
10
|
-
const { dir, name, ext } = path.parse(filepath);
|
|
11
|
-
const refactoredPath = path.join(dir, `${name}.refactored${ext}`);
|
|
12
|
-
if (options.apply) {
|
|
13
|
-
try {
|
|
14
|
-
const refactoredCode = await fs.readFile(refactoredPath, 'utf-8');
|
|
15
|
-
await fs.writeFile(filepath, refactoredCode, 'utf-8');
|
|
16
|
-
await fs.unlink(refactoredPath);
|
|
17
|
-
console.log(`♻️ Applied refactor: Overwrote ${filepath} and removed ${refactoredPath}`);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
console.error(`❌ No saved refactor found at ${refactoredPath}`);
|
|
21
|
-
}
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const content = await fs.readFile(filepath, 'utf-8');
|
|
25
|
-
const response = await runModulePipeline([addCommentsModule], { content });
|
|
26
|
-
if (!response.content.trim())
|
|
27
|
-
throw new Error('⚠️ Model returned empty result');
|
|
28
|
-
await fs.writeFile(refactoredPath, response.content, 'utf-8');
|
|
29
|
-
console.log(`✅ Refactored code saved to: ${refactoredPath}`);
|
|
30
|
-
console.log(`ℹ️ Run again with '--apply' to overwrite the original.`);
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
console.error('❌ Error in refactor command:', err instanceof Error ? err.message : err);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import { generateTestsModule } from '../pipeline/modules/generateTestsModule.js';
|
|
3
|
-
import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
|
|
4
|
-
import { runModulePipeline } from '../pipeline/runModulePipeline.js';
|
|
5
|
-
export async function generateTests(filepath) {
|
|
6
|
-
try {
|
|
7
|
-
const content = await fs.readFile(filepath, 'utf-8');
|
|
8
|
-
const result = await runModulePipeline([generateTestsModule, cleanupModule], { content, filepath });
|
|
9
|
-
console.log('✅ Test generated and cleaned up.');
|
|
10
|
-
}
|
|
11
|
-
catch (err) {
|
|
12
|
-
console.error('❌ Error generating tests:', err.message);
|
|
13
|
-
}
|
|
14
|
-
}
|