@technicalshree/auto-fix 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +408 -0
- package/dist/cli.js +288 -0
- package/dist/config/defaults.js +44 -0
- package/dist/config/loadConfig.js +44 -0
- package/dist/core/detectEnvironment.js +93 -0
- package/dist/core/executor.js +124 -0
- package/dist/core/planBuilder.js +45 -0
- package/dist/core/run.js +125 -0
- package/dist/core/safety.js +9 -0
- package/dist/core/snapshots.js +18 -0
- package/dist/core/undo.js +53 -0
- package/dist/report/renderSummary.js +73 -0
- package/dist/report/writeReport.js +9 -0
- package/dist/subsystems/checks.js +118 -0
- package/dist/subsystems/docker.js +56 -0
- package/dist/subsystems/node.js +124 -0
- package/dist/subsystems/python.js +64 -0
- package/dist/types.js +1 -0
- package/dist/ui/renderer.js +161 -0
- package/dist/utils/colors.js +20 -0
- package/dist/utils/fs.js +60 -0
- package/dist/utils/process.js +17 -0
- package/dist/utils/tty.js +3 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# auto-fix
|
|
2
|
+
|
|
3
|
+
`auto-fix` is a TypeScript CLI that detects local development environment issues and applies safe, explainable fixes for Node, Python, and Docker-based projects.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Detects project stack (Node, Python, Docker Compose)
|
|
8
|
+
- Diagnoses common local setup problems
|
|
9
|
+
- Builds an explicit fix plan before execution
|
|
10
|
+
- Runs safe fixes by default
|
|
11
|
+
- Supports gated destructive cleanup (`--deep`, `--approve`)
|
|
12
|
+
- Writes run reports and snapshot metadata for rollback
|
|
13
|
+
- Supports best-effort `undo` for snapshotted changes
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Node.js 18+
|
|
18
|
+
- npm (or compatible Node package manager for development)
|
|
19
|
+
- Optional tools (used only when relevant to your project):
|
|
20
|
+
- Python + `pip`/`uv`/`poetry`/`pipenv`
|
|
21
|
+
- Docker + Docker Compose
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### 1) Install from npm (recommended)
|
|
26
|
+
|
|
27
|
+
Run directly without installing globally:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx auto-fix doctor
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Install globally:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @technicalshree/auto-fix
|
|
37
|
+
auto-fix --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2) Install from source (development)
|
|
41
|
+
|
|
42
|
+
Clone and install dependencies:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone <your-repo-url>
|
|
46
|
+
cd ts-cli-tool
|
|
47
|
+
npm install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Build the CLI:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Run locally:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm start
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Use as a CLI command (`auto-fix`)
|
|
63
|
+
|
|
64
|
+
After build, the executable is exposed via:
|
|
65
|
+
|
|
66
|
+
- Bin name: `auto-fix`
|
|
67
|
+
- Entry point: `dist/cli.js`
|
|
68
|
+
|
|
69
|
+
You can run it in either of these ways:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm start -- doctor
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
node dist/cli.js doctor
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
To install globally from this repo:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run build
|
|
83
|
+
npm link
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then use:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
auto-fix
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
To remove global link:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm unlink -g auto-fix
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Publish to npm
|
|
99
|
+
|
|
100
|
+
1. Ensure you are logged in to npm:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm login
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
2. Bump the version:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm version patch
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
3. Build and validate the package contents:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm run build
|
|
116
|
+
npm pack --dry-run --cache /tmp/npm-cache-autofix
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
4. Publish:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm publish
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Quick start
|
|
126
|
+
|
|
127
|
+
Run safe fixes (default behavior):
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
auto-fix
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Diagnosis only (no changes):
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
auto-fix doctor
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Preview plan without executing:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
auto-fix plan
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Show latest report as JSON:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
auto-fix report --json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Rollback latest run (best-effort):
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
auto-fix undo
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Clear global package manager caches:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
auto-fix clear-npm-cache
|
|
161
|
+
auto-fix clear-yarn-cache
|
|
162
|
+
auto-fix clear-pnpm-cache
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Commands
|
|
166
|
+
|
|
167
|
+
`auto-fix [command] [flags]`
|
|
168
|
+
|
|
169
|
+
- Default command (`auto-fix`): detect + execute safe fixes
|
|
170
|
+
- `doctor`: detection and diagnosis only (no modifications)
|
|
171
|
+
- `plan`: prints execution plan (same as dry-run)
|
|
172
|
+
- `report`: reads latest report (`--run` runs fresh first)
|
|
173
|
+
- `undo`: best-effort restore using latest run snapshots
|
|
174
|
+
- `clear-npm-cache`: runs `npm cache clean --force`
|
|
175
|
+
- `clear-yarn-cache`: runs `yarn cache clean`
|
|
176
|
+
- `clear-pnpm-cache`: runs `pnpm store prune`
|
|
177
|
+
- `help`: show CLI help
|
|
178
|
+
|
|
179
|
+
## Flags (all)
|
|
180
|
+
|
|
181
|
+
### Safety and execution control
|
|
182
|
+
|
|
183
|
+
- `--dry-run`: show actions, do not execute
|
|
184
|
+
- `--deep`: enable destructive cleanup steps (for example deleting `node_modules`)
|
|
185
|
+
- `--approve`: skip interactive prompts and allow destructive steps
|
|
186
|
+
- `--force-fresh`: allow fresh rebuild actions (requires `--deep` or `--approve`)
|
|
187
|
+
- `--focus <subsystem>`: restrict execution to one subsystem
|
|
188
|
+
- Allowed values: `node`, `python`, `docker`, `all`
|
|
189
|
+
- `--checks <list>`: checks phase selector
|
|
190
|
+
- Comma-separated values from: `lint,test,format`
|
|
191
|
+
- Example: `--checks lint,test`
|
|
192
|
+
- `--kill-ports [ports]`: enable port cleanup
|
|
193
|
+
- Optional comma-separated ports
|
|
194
|
+
- If omitted, defaults are used from config
|
|
195
|
+
|
|
196
|
+
### Output and reporting
|
|
197
|
+
|
|
198
|
+
- `--verbose`: print commands and command outputs
|
|
199
|
+
- `--quiet`: minimal output, still prints final summary
|
|
200
|
+
- `--no-color`: disable ANSI colors
|
|
201
|
+
- `--json`: print machine-readable JSON report to stdout
|
|
202
|
+
- `--report-path <path>`: override report directory path
|
|
203
|
+
- `--run`: with `report`, run a fresh execution instead of reading latest
|
|
204
|
+
|
|
205
|
+
## Typical usage patterns
|
|
206
|
+
|
|
207
|
+
Safe default run:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
auto-fix
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Plan before execution:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
auto-fix plan
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Deep cleanup with explicit approval:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
auto-fix --deep --approve
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Only Node subsystem:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
auto-fix --focus node
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Run only lint + test checks:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
auto-fix --checks lint,test
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Kill default configured ports then execute:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
auto-fix --kill-ports
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Kill specific ports:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
auto-fix --kill-ports 3000,5173,9229
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Read latest report:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
auto-fix report --json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Trigger run and emit JSON in one command:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
auto-fix report --run --json
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Clear npm global cache:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
auto-fix clear-npm-cache
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Clear yarn global cache:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
auto-fix clear-yarn-cache
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Clear pnpm global cache:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
auto-fix clear-pnpm-cache
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Configuration
|
|
280
|
+
|
|
281
|
+
`auto-fix` works without config. To customize behavior, create `.autofix.yml` in project root.
|
|
282
|
+
|
|
283
|
+
The loader searches current directory upward until git root.
|
|
284
|
+
|
|
285
|
+
### Example `.autofix.yml`
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
version: 1
|
|
289
|
+
|
|
290
|
+
ports:
|
|
291
|
+
default: [3000, 5173, 8000, 8080]
|
|
292
|
+
extra: [9229]
|
|
293
|
+
|
|
294
|
+
node:
|
|
295
|
+
package_manager: auto # auto | npm | pnpm | yarn
|
|
296
|
+
deep_cleanup:
|
|
297
|
+
remove_node_modules: true
|
|
298
|
+
remove_lockfile: false
|
|
299
|
+
caches:
|
|
300
|
+
next: true
|
|
301
|
+
vite: true
|
|
302
|
+
directories: [".turbo", ".cache"]
|
|
303
|
+
|
|
304
|
+
python:
|
|
305
|
+
venv_path: .venv
|
|
306
|
+
install:
|
|
307
|
+
prefer: uv # uv | pip | poetry | pipenv | auto
|
|
308
|
+
tools:
|
|
309
|
+
format: ["ruff format", "black ."]
|
|
310
|
+
lint: ["ruff check ."]
|
|
311
|
+
test: ["pytest -q"]
|
|
312
|
+
|
|
313
|
+
docker:
|
|
314
|
+
compose_file: auto
|
|
315
|
+
safe_down: true
|
|
316
|
+
rebuild: true
|
|
317
|
+
prune: false
|
|
318
|
+
|
|
319
|
+
checks:
|
|
320
|
+
default: [lint, format, test]
|
|
321
|
+
|
|
322
|
+
output:
|
|
323
|
+
report_dir: .autofix/reports
|
|
324
|
+
snapshot_dir: .autofix/snapshots
|
|
325
|
+
verbosity: normal # quiet | normal | verbose
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Reports and artifacts
|
|
329
|
+
|
|
330
|
+
By default:
|
|
331
|
+
|
|
332
|
+
- Reports: `.autofix/reports/`
|
|
333
|
+
- Latest report: `.autofix/reports/latest.json`
|
|
334
|
+
- Snapshots: `.autofix/snapshots/`
|
|
335
|
+
|
|
336
|
+
`auto-fix` also ensures `.autofix/` is in `.gitignore`.
|
|
337
|
+
|
|
338
|
+
If the configured snapshot directory is not writable, snapshots fall back to a temp directory:
|
|
339
|
+
|
|
340
|
+
- macOS/Linux pattern: `/tmp/autofix/<run_id>`
|
|
341
|
+
|
|
342
|
+
## Undo behavior (important)
|
|
343
|
+
|
|
344
|
+
`auto-fix undo` is best-effort and limited to steps that:
|
|
345
|
+
|
|
346
|
+
- Are marked undoable
|
|
347
|
+
- Have snapshot paths recorded in the report
|
|
348
|
+
- Still have snapshot files available
|
|
349
|
+
|
|
350
|
+
Undo cannot restore changes that were never snapshotted or are irreversible by nature.
|
|
351
|
+
|
|
352
|
+
## Exit codes
|
|
353
|
+
|
|
354
|
+
- `0`: successful run or expected non-error output
|
|
355
|
+
- `1`: runtime error, missing latest report for `report`/`undo`, or `doctor` found issues
|
|
356
|
+
|
|
357
|
+
## Development
|
|
358
|
+
|
|
359
|
+
### Scripts
|
|
360
|
+
|
|
361
|
+
- `npm run dev`: run CLI from source with `tsx` (`src/cli.ts`)
|
|
362
|
+
- `npm run build`: compile TypeScript to `dist/`
|
|
363
|
+
- `npm start`: run built CLI (`dist/cli.js`)
|
|
364
|
+
- `npm test`: build silently and run Node test runner
|
|
365
|
+
- `npm run lint`: TypeScript type-check only (`tsc --noEmit`)
|
|
366
|
+
|
|
367
|
+
### Local development workflow
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
npm install
|
|
371
|
+
npm run lint
|
|
372
|
+
npm test
|
|
373
|
+
npm run dev -- doctor
|
|
374
|
+
npm run build
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Project structure
|
|
378
|
+
|
|
379
|
+
- `src/cli.ts`: CLI entrypoint and argument parsing
|
|
380
|
+
- `src/core/`: detection, planning, execution, safety, undo
|
|
381
|
+
- `src/config/`: defaults and config loading
|
|
382
|
+
- `src/report/`: summary and report writing
|
|
383
|
+
- `src/subsystems/`: subsystem-specific checks/fixes
|
|
384
|
+
- `src/ui/`: terminal renderer and progress hooks
|
|
385
|
+
- `dist/`: compiled JavaScript output
|
|
386
|
+
- `docs/`: product requirement docs and notes
|
|
387
|
+
|
|
388
|
+
## Troubleshooting
|
|
389
|
+
|
|
390
|
+
`auto-fix` command not found:
|
|
391
|
+
|
|
392
|
+
- Run `npm run build` first
|
|
393
|
+
- Use `node dist/cli.js ...` directly
|
|
394
|
+
- If using global link, run `npm link` in project root
|
|
395
|
+
|
|
396
|
+
No report found for `report` or `undo`:
|
|
397
|
+
|
|
398
|
+
- Run `auto-fix` once first
|
|
399
|
+
- Check custom `--report-path` usage
|
|
400
|
+
- Verify `.autofix/reports/latest.json` exists
|
|
401
|
+
|
|
402
|
+
Non-interactive environments behave conservatively:
|
|
403
|
+
|
|
404
|
+
- In polyglot projects (Node + Python + Docker), non-interactive mode without `--approve` may limit execution to a safe subset
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
ISC
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { loadConfig } from "./config/loadConfig.js";
|
|
5
|
+
import { runAutoFix } from "./core/run.js";
|
|
6
|
+
import { renderSummary, renderQuietSummary } from "./report/renderSummary.js";
|
|
7
|
+
import { writeRunReport } from "./report/writeReport.js";
|
|
8
|
+
import { isInteractive } from "./utils/tty.js";
|
|
9
|
+
import { readJsonFile } from "./utils/fs.js";
|
|
10
|
+
import { undoLatest } from "./core/undo.js";
|
|
11
|
+
import { createRenderer } from "./ui/renderer.js";
|
|
12
|
+
import { runShellCommand } from "./utils/process.js";
|
|
13
|
+
function makeRunId() {
|
|
14
|
+
return `${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
|
|
15
|
+
}
|
|
16
|
+
function parseCsv(value) {
|
|
17
|
+
return value
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((x) => x.trim())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
function printHelp() {
|
|
23
|
+
console.log(`
|
|
24
|
+
auto-fix — detect, diagnose, and safely fix common dev environment issues
|
|
25
|
+
|
|
26
|
+
USAGE
|
|
27
|
+
auto-fix [command] [flags]
|
|
28
|
+
|
|
29
|
+
COMMANDS
|
|
30
|
+
(default) Run safe fixes (detect project, execute fix plan)
|
|
31
|
+
doctor Diagnosis only — no changes, collects facts + recommendations
|
|
32
|
+
plan Print the exact plan that would run (no changes)
|
|
33
|
+
report Show the last run's report (use --json for machine-readable)
|
|
34
|
+
undo Best-effort rollback of the last run
|
|
35
|
+
clear-npm-cache Clear global npm package cache
|
|
36
|
+
clear-yarn-cache Clear global yarn package cache
|
|
37
|
+
clear-pnpm-cache Clear global pnpm package cache
|
|
38
|
+
help Show this help message
|
|
39
|
+
|
|
40
|
+
SAFETY & CONTROL
|
|
41
|
+
--dry-run Show actions, run nothing (same as plan)
|
|
42
|
+
--deep Enable destructive cleanup steps (e.g. delete node_modules)
|
|
43
|
+
--approve Skip prompts, allow destructive steps (still logs)
|
|
44
|
+
--force-fresh Allow fresh rebuild actions (requires --deep or --approve)
|
|
45
|
+
--focus <subsystem> Restrict to: node | python | docker | all (default: all)
|
|
46
|
+
--checks <list> Run checks phase: lint,test,format (comma-separated)
|
|
47
|
+
--kill-ports [ports] Enable port cleanup. Optional comma-separated ports
|
|
48
|
+
|
|
49
|
+
OUTPUT & REPORTING
|
|
50
|
+
--verbose Print executed commands + stdout/stderr
|
|
51
|
+
--quiet Minimal output (still prints final summary)
|
|
52
|
+
--no-color Disable ANSI coloring
|
|
53
|
+
--json Print JSON report to stdout
|
|
54
|
+
--report-path <path> Override where to write reports
|
|
55
|
+
--run With 'report' command: run fresh instead of reading latest
|
|
56
|
+
|
|
57
|
+
EXAMPLES
|
|
58
|
+
auto-fix Run safe fixes for detected project
|
|
59
|
+
auto-fix doctor Diagnose without changing anything
|
|
60
|
+
auto-fix plan Preview what would run
|
|
61
|
+
auto-fix --deep Run with destructive cleanup enabled
|
|
62
|
+
auto-fix --kill-ports Kill processes on default ports first
|
|
63
|
+
auto-fix report --json Print last run report as JSON
|
|
64
|
+
auto-fix undo Rollback the last run (best-effort)
|
|
65
|
+
auto-fix clear-npm-cache Clear npm cache
|
|
66
|
+
auto-fix clear-yarn-cache Clear yarn cache
|
|
67
|
+
auto-fix clear-pnpm-cache Clear pnpm cache
|
|
68
|
+
|
|
69
|
+
CONFIG
|
|
70
|
+
Place .autofix.yml in your project root for custom settings.
|
|
71
|
+
auto-fix works out-of-the-box without any config file.
|
|
72
|
+
`.trim());
|
|
73
|
+
}
|
|
74
|
+
function parseArgs(argv) {
|
|
75
|
+
if (argv.length === 0)
|
|
76
|
+
return { command: "run", flags: defaultFlags(), help: false };
|
|
77
|
+
const hasHelp = argv.includes("--help") || argv.includes("-h") || argv[0] === "help";
|
|
78
|
+
if (hasHelp)
|
|
79
|
+
return { command: "run", flags: defaultFlags(), help: true };
|
|
80
|
+
const [first, ...rest] = argv;
|
|
81
|
+
const command = first === "doctor" ||
|
|
82
|
+
first === "plan" ||
|
|
83
|
+
first === "report" ||
|
|
84
|
+
first === "undo" ||
|
|
85
|
+
first === "clear-npm-cache" ||
|
|
86
|
+
first === "clear-yarn-cache" ||
|
|
87
|
+
first === "clear-pnpm-cache"
|
|
88
|
+
? first
|
|
89
|
+
: "run";
|
|
90
|
+
const args = command === "run" ? argv : rest;
|
|
91
|
+
const flags = defaultFlags();
|
|
92
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
93
|
+
const arg = args[i];
|
|
94
|
+
if (arg === "--dry-run")
|
|
95
|
+
flags.dryRun = true;
|
|
96
|
+
else if (arg === "--deep")
|
|
97
|
+
flags.deep = true;
|
|
98
|
+
else if (arg === "--approve")
|
|
99
|
+
flags.approve = true;
|
|
100
|
+
else if (arg === "--force-fresh")
|
|
101
|
+
flags.forceFresh = true;
|
|
102
|
+
else if (arg === "--verbose")
|
|
103
|
+
flags.verbose = true;
|
|
104
|
+
else if (arg === "--quiet")
|
|
105
|
+
flags.quiet = true;
|
|
106
|
+
else if (arg === "--no-color")
|
|
107
|
+
flags.noColor = true;
|
|
108
|
+
else if (arg === "--json")
|
|
109
|
+
flags.json = true;
|
|
110
|
+
else if (arg === "--run")
|
|
111
|
+
flags.run = true;
|
|
112
|
+
else if (arg === "--focus" && args[i + 1]) {
|
|
113
|
+
const f = args[i + 1];
|
|
114
|
+
if (f === "node" || f === "python" || f === "docker" || f === "all")
|
|
115
|
+
flags.focus = f;
|
|
116
|
+
i += 1;
|
|
117
|
+
}
|
|
118
|
+
else if (arg === "--checks" && args[i + 1]) {
|
|
119
|
+
flags.checks = parseCsv(args[i + 1]).filter((v) => v === "lint" || v === "test" || v === "format");
|
|
120
|
+
i += 1;
|
|
121
|
+
}
|
|
122
|
+
else if (arg === "--kill-ports") {
|
|
123
|
+
if (args[i + 1] && !args[i + 1].startsWith("--")) {
|
|
124
|
+
flags.killPorts = parseCsv(args[i + 1]).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0);
|
|
125
|
+
i += 1;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
flags.killPorts = [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (arg === "--report-path" && args[i + 1]) {
|
|
132
|
+
flags.reportPath = args[i + 1];
|
|
133
|
+
i += 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (command === "plan")
|
|
137
|
+
flags.dryRun = true;
|
|
138
|
+
return { command, flags, help: false };
|
|
139
|
+
}
|
|
140
|
+
async function runCacheCommand(command, cwd) {
|
|
141
|
+
const shellCommand = command === "clear-npm-cache"
|
|
142
|
+
? "npm cache clean --force"
|
|
143
|
+
: command === "clear-yarn-cache"
|
|
144
|
+
? "yarn cache clean"
|
|
145
|
+
: "pnpm store prune";
|
|
146
|
+
console.log(`Running: ${shellCommand}`);
|
|
147
|
+
const result = await runShellCommand(shellCommand, cwd);
|
|
148
|
+
if (result.stdout.trim())
|
|
149
|
+
console.log(result.stdout.trim());
|
|
150
|
+
if (result.stderr.trim())
|
|
151
|
+
console.error(result.stderr.trim());
|
|
152
|
+
if (!result.success) {
|
|
153
|
+
console.error(`Command failed with exit code ${result.code}`);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
console.log("Cache cleared successfully.");
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function defaultFlags() {
|
|
160
|
+
return {
|
|
161
|
+
dryRun: false,
|
|
162
|
+
deep: false,
|
|
163
|
+
approve: false,
|
|
164
|
+
forceFresh: false,
|
|
165
|
+
focus: "all",
|
|
166
|
+
verbose: false,
|
|
167
|
+
quiet: false,
|
|
168
|
+
noColor: false,
|
|
169
|
+
json: false,
|
|
170
|
+
run: false,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function main() {
|
|
174
|
+
const { command, flags, help } = parseArgs(process.argv.slice(2));
|
|
175
|
+
if (help) {
|
|
176
|
+
printHelp();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const cwd = process.cwd();
|
|
180
|
+
const interactive = isInteractive();
|
|
181
|
+
const ui = createRenderer(flags, interactive);
|
|
182
|
+
const ctx = {
|
|
183
|
+
command,
|
|
184
|
+
cwd,
|
|
185
|
+
runId: makeRunId(),
|
|
186
|
+
flags,
|
|
187
|
+
interactive,
|
|
188
|
+
};
|
|
189
|
+
const { config } = await loadConfig(cwd);
|
|
190
|
+
const reportDir = path.resolve(cwd, flags.reportPath ?? config.output.report_dir);
|
|
191
|
+
// ── cache clear commands ──
|
|
192
|
+
if (command === "clear-npm-cache" || command === "clear-yarn-cache" || command === "clear-pnpm-cache") {
|
|
193
|
+
const ok = await runCacheCommand(command, cwd);
|
|
194
|
+
if (!ok)
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// ── undo ──
|
|
199
|
+
if (command === "undo") {
|
|
200
|
+
ui.intro("undo");
|
|
201
|
+
ui.startTask("Reading latest report\u2026");
|
|
202
|
+
const { report, entries } = await undoLatest(path.join(reportDir, "latest.json"), cwd);
|
|
203
|
+
if (!report) {
|
|
204
|
+
ui.stopTask("\u2717 No previous report found");
|
|
205
|
+
if (!ui.isRich)
|
|
206
|
+
console.error("No previous report found for undo.");
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
ui.stopTask("\u2714 Report loaded");
|
|
211
|
+
ui.showDetection(report.detection);
|
|
212
|
+
const failed = entries.reduce((acc, e) => acc + e.failed.length, 0);
|
|
213
|
+
const restored = entries.reduce((acc, e) => acc + e.restored.length, 0);
|
|
214
|
+
const missing = entries.reduce((acc, e) => acc + e.missingSnapshot.length, 0);
|
|
215
|
+
const skipped = entries.reduce((acc, e) => acc + e.skipped.length, 0);
|
|
216
|
+
ui.showUndoResults(restored, skipped, missing, failed);
|
|
217
|
+
if (!ui.isRich) {
|
|
218
|
+
console.log(`Restored: ${restored}, Skipped: ${skipped}, Missing snapshot: ${missing}, Failed: ${failed}`);
|
|
219
|
+
}
|
|
220
|
+
const next = entries.find((e) => e.nextBestAction)?.nextBestAction ?? "Run auto-fix doctor to validate";
|
|
221
|
+
ui.outro(next);
|
|
222
|
+
if (!ui.isRich)
|
|
223
|
+
console.log(`Next: ${next}`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// ── report (read-only) ──
|
|
227
|
+
if (command === "report" && !flags.run) {
|
|
228
|
+
ui.intro("report");
|
|
229
|
+
const latest = await readJsonFile(path.join(reportDir, "latest.json"));
|
|
230
|
+
if (!latest) {
|
|
231
|
+
ui.showReportInfo("No report found at latest.json");
|
|
232
|
+
if (!ui.isRich)
|
|
233
|
+
console.error("No report found at latest.json");
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (flags.json) {
|
|
238
|
+
console.log(JSON.stringify(latest, null, 2));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
ui.showReportInfo("Report loaded. Use --json to print machine-readable output.");
|
|
242
|
+
if (!ui.isRich)
|
|
243
|
+
console.log("Use --json to print machine-readable report.");
|
|
244
|
+
}
|
|
245
|
+
ui.outro("auto-fix report --json");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// ── run / doctor / plan ──
|
|
249
|
+
const effectiveCommand = command === "report" ? "run" : command;
|
|
250
|
+
ui.intro(effectiveCommand);
|
|
251
|
+
const callbacks = ui.isRich
|
|
252
|
+
? {
|
|
253
|
+
onDetection: (det) => ui.showDetection(det),
|
|
254
|
+
onPlanReady: (steps) => {
|
|
255
|
+
if (effectiveCommand === "plan" || effectiveCommand === "doctor") {
|
|
256
|
+
ui.showPlan(steps, effectiveCommand === "doctor" ? "Diagnosis" : undefined);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
stepHooks: effectiveCommand === "run" ? ui.createStepHooks() : undefined,
|
|
260
|
+
}
|
|
261
|
+
: undefined;
|
|
262
|
+
const result = await runAutoFix({ ...ctx, command: effectiveCommand }, config, reportDir, callbacks);
|
|
263
|
+
const report = result.report;
|
|
264
|
+
await writeRunReport(report, reportDir);
|
|
265
|
+
if (ui.isRich) {
|
|
266
|
+
ui.showResults(report.summary);
|
|
267
|
+
ui.outro(report.summary.nextBestAction);
|
|
268
|
+
}
|
|
269
|
+
// Fallback text output for non-rich modes
|
|
270
|
+
if (!ui.isRich) {
|
|
271
|
+
if (flags.quiet) {
|
|
272
|
+
console.log(renderQuietSummary(report, !flags.noColor));
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
console.log(renderSummary(report, !flags.noColor));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (flags.json || command === "report") {
|
|
279
|
+
console.log(JSON.stringify(report, null, 2));
|
|
280
|
+
}
|
|
281
|
+
if (effectiveCommand === "doctor" && report.detection.issues.length > 0) {
|
|
282
|
+
process.exitCode = 1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
main().catch((error) => {
|
|
286
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
});
|