pi-mono-all 1.0.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/CHANGELOG.md +13 -0
- package/LICENCE.md +7 -0
- package/node_modules/pi-common/package.json +22 -0
- package/node_modules/pi-common/src/auth-config.ts +290 -0
- package/node_modules/pi-common/src/auth.ts +63 -0
- package/node_modules/pi-common/src/cache.ts +60 -0
- package/node_modules/pi-common/src/errors.ts +47 -0
- package/node_modules/pi-common/src/http-client.ts +118 -0
- package/node_modules/pi-common/src/index.ts +7 -0
- package/node_modules/pi-common/src/rate-limiter.ts +32 -0
- package/node_modules/pi-common/src/tool-result.ts +27 -0
- package/node_modules/pi-mono-ask-user-question/CHANGELOG.md +185 -0
- package/node_modules/pi-mono-ask-user-question/README.md +226 -0
- package/node_modules/pi-mono-ask-user-question/index.ts +923 -0
- package/node_modules/pi-mono-ask-user-question/package.json +29 -0
- package/node_modules/pi-mono-auto-fix/CHANGELOG.md +59 -0
- package/node_modules/pi-mono-auto-fix/README.md +77 -0
- package/node_modules/pi-mono-auto-fix/index.ts +488 -0
- package/node_modules/pi-mono-auto-fix/package.json +23 -0
- package/node_modules/pi-mono-btw/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-btw/README.md +24 -0
- package/node_modules/pi-mono-btw/index.ts +499 -0
- package/node_modules/pi-mono-btw/package.json +29 -0
- package/node_modules/pi-mono-clear/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-clear/README.md +40 -0
- package/node_modules/pi-mono-clear/index.ts +45 -0
- package/node_modules/pi-mono-clear/package.json +29 -0
- package/node_modules/pi-mono-context/CHANGELOG.md +12 -0
- package/node_modules/pi-mono-context/README.md +74 -0
- package/node_modules/pi-mono-context/index.ts +641 -0
- package/node_modules/pi-mono-context/package.json +29 -0
- package/node_modules/pi-mono-context-guard/CHANGELOG.md +195 -0
- package/node_modules/pi-mono-context-guard/README.md +81 -0
- package/node_modules/pi-mono-context-guard/index.ts +212 -0
- package/node_modules/pi-mono-context-guard/package.json +23 -0
- package/node_modules/pi-mono-figma/CHANGELOG.md +59 -0
- package/node_modules/pi-mono-figma/README.md +236 -0
- package/node_modules/pi-mono-figma/__tests__/code-connect.test.ts +32 -0
- package/node_modules/pi-mono-figma/__tests__/figma-assets.test.ts +38 -0
- package/node_modules/pi-mono-figma/__tests__/figma-component-hints.test.ts +23 -0
- package/node_modules/pi-mono-figma/__tests__/figma-implementation-layout.test.ts +47 -0
- package/node_modules/pi-mono-figma/__tests__/figma-search.test.ts +51 -0
- package/node_modules/pi-mono-figma/__tests__/figma-summarizer.test.ts +65 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/complex-auto-layout.json +115 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/component-instance.json +50 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/hidden-and-vectors.json +28 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/variables-and-styles.json +40 -0
- package/node_modules/pi-mono-figma/docs/live-selection-bridge.md +16 -0
- package/node_modules/pi-mono-figma/index.ts +6 -0
- package/node_modules/pi-mono-figma/package.json +33 -0
- package/node_modules/pi-mono-figma/skills/figma/SKILL.md +143 -0
- package/node_modules/pi-mono-figma/src/code-connect.ts +110 -0
- package/node_modules/pi-mono-figma/src/figma-assets.ts +146 -0
- package/node_modules/pi-mono-figma/src/figma-cache.ts +6 -0
- package/node_modules/pi-mono-figma/src/figma-client.ts +471 -0
- package/node_modules/pi-mono-figma/src/figma-component-hints.ts +87 -0
- package/node_modules/pi-mono-figma/src/figma-implementation.ts +264 -0
- package/node_modules/pi-mono-figma/src/figma-schemas.ts +139 -0
- package/node_modules/pi-mono-figma/src/figma-search.ts +195 -0
- package/node_modules/pi-mono-figma/src/figma-summarizer.ts +673 -0
- package/node_modules/pi-mono-figma/src/figma-tokens.ts +57 -0
- package/node_modules/pi-mono-figma/src/figma-tools.ts +352 -0
- package/node_modules/pi-mono-linear/CHANGELOG.md +44 -0
- package/node_modules/pi-mono-linear/README.md +159 -0
- package/node_modules/pi-mono-linear/index.ts +6 -0
- package/node_modules/pi-mono-linear/package.json +30 -0
- package/node_modules/pi-mono-linear/skills/linear/SKILL.md +107 -0
- package/node_modules/pi-mono-linear/src/linear-client.ts +339 -0
- package/node_modules/pi-mono-linear/src/linear-queries.ts +101 -0
- package/node_modules/pi-mono-linear/src/linear-schemas.ts +90 -0
- package/node_modules/pi-mono-linear/src/linear-tools.ts +362 -0
- package/node_modules/pi-mono-loop/CHANGELOG.md +163 -0
- package/node_modules/pi-mono-loop/README.md +54 -0
- package/node_modules/pi-mono-loop/index.ts +291 -0
- package/node_modules/pi-mono-loop/package.json +26 -0
- package/node_modules/pi-mono-multi-edit/CHANGELOG.md +232 -0
- package/node_modules/pi-mono-multi-edit/README.md +244 -0
- package/node_modules/pi-mono-multi-edit/__tests__/classic.test.ts +277 -0
- package/node_modules/pi-mono-multi-edit/__tests__/diff.test.ts +77 -0
- package/node_modules/pi-mono-multi-edit/__tests__/patch.test.ts +287 -0
- package/node_modules/pi-mono-multi-edit/benchmark-edits.ts +966 -0
- package/node_modules/pi-mono-multi-edit/classic.ts +435 -0
- package/node_modules/pi-mono-multi-edit/diff.ts +143 -0
- package/node_modules/pi-mono-multi-edit/index.ts +266 -0
- package/node_modules/pi-mono-multi-edit/package.json +37 -0
- package/node_modules/pi-mono-multi-edit/patch.ts +463 -0
- package/node_modules/pi-mono-multi-edit/types.ts +53 -0
- package/node_modules/pi-mono-multi-edit/workspace.ts +85 -0
- package/node_modules/pi-mono-review/CHANGELOG.md +190 -0
- package/node_modules/pi-mono-review/README.md +30 -0
- package/node_modules/pi-mono-review/common.ts +930 -0
- package/node_modules/pi-mono-review/index.ts +8 -0
- package/node_modules/pi-mono-review/package.json +29 -0
- package/node_modules/pi-mono-review/review-tui.ts +194 -0
- package/node_modules/pi-mono-review/review.ts +119 -0
- package/node_modules/pi-mono-review/reviewer.ts +339 -0
- package/node_modules/pi-mono-sentinel/CHANGELOG.md +158 -0
- package/node_modules/pi-mono-sentinel/README.md +87 -0
- package/node_modules/pi-mono-sentinel/__tests__/output-scanner.test.ts +109 -0
- package/node_modules/pi-mono-sentinel/__tests__/permissions.test.ts +202 -0
- package/node_modules/pi-mono-sentinel/__tests__/whitelist.test.ts +59 -0
- package/node_modules/pi-mono-sentinel/guards/execution-tracker.ts +281 -0
- package/node_modules/pi-mono-sentinel/guards/output-scanner.ts +232 -0
- package/node_modules/pi-mono-sentinel/guards/permission-gate.ts +170 -0
- package/node_modules/pi-mono-sentinel/index.ts +43 -0
- package/node_modules/pi-mono-sentinel/package.json +26 -0
- package/node_modules/pi-mono-sentinel/patterns/permissions.ts +175 -0
- package/node_modules/pi-mono-sentinel/patterns/read-targets.ts +104 -0
- package/node_modules/pi-mono-sentinel/patterns/secrets.ts +143 -0
- package/node_modules/pi-mono-sentinel/session.ts +95 -0
- package/node_modules/pi-mono-sentinel/specs/2026/04/sentinel/001-permission-gate.md +145 -0
- package/node_modules/pi-mono-sentinel/types.ts +39 -0
- package/node_modules/pi-mono-sentinel/whitelist.ts +86 -0
- package/node_modules/pi-mono-simplify/CHANGELOG.md +163 -0
- package/node_modules/pi-mono-simplify/README.md +56 -0
- package/node_modules/pi-mono-simplify/index.ts +78 -0
- package/node_modules/pi-mono-simplify/package.json +29 -0
- package/node_modules/pi-mono-status-line/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-status-line/README.md +96 -0
- package/node_modules/pi-mono-status-line/basic.ts +89 -0
- package/node_modules/pi-mono-status-line/expert.ts +689 -0
- package/node_modules/pi-mono-status-line/index.ts +54 -0
- package/node_modules/pi-mono-status-line/package.json +29 -0
- package/node_modules/pi-mono-team-mode/CHANGELOG.md +278 -0
- package/node_modules/pi-mono-team-mode/README.md +246 -0
- package/node_modules/pi-mono-team-mode/__tests__/agent-manager-transient.test.ts +75 -0
- package/node_modules/pi-mono-team-mode/__tests__/delegation-manager.test.ts +118 -0
- package/node_modules/pi-mono-team-mode/__tests__/formatters.test.ts +104 -0
- package/node_modules/pi-mono-team-mode/__tests__/model-config.test.ts +272 -0
- package/node_modules/pi-mono-team-mode/__tests__/notification-box.test.ts +34 -0
- package/node_modules/pi-mono-team-mode/__tests__/parallel-utils.test.ts +32 -0
- package/node_modules/pi-mono-team-mode/__tests__/pi-stream-parser.test.ts +64 -0
- package/node_modules/pi-mono-team-mode/__tests__/prompts.test.ts +106 -0
- package/node_modules/pi-mono-team-mode/__tests__/store.test.ts +164 -0
- package/node_modules/pi-mono-team-mode/__tests__/tasks.test.ts +267 -0
- package/node_modules/pi-mono-team-mode/__tests__/teammate-specs.test.ts +114 -0
- package/node_modules/pi-mono-team-mode/__tests__/widget.test.ts +41 -0
- package/node_modules/pi-mono-team-mode/__tests__/worktree.test.ts +78 -0
- package/node_modules/pi-mono-team-mode/core/chain-utils.ts +90 -0
- package/node_modules/pi-mono-team-mode/core/fs-utils.ts +44 -0
- package/node_modules/pi-mono-team-mode/core/model-config.ts +432 -0
- package/node_modules/pi-mono-team-mode/core/parallel-utils.ts +48 -0
- package/node_modules/pi-mono-team-mode/core/prompts.ts +158 -0
- package/node_modules/pi-mono-team-mode/core/store.ts +156 -0
- package/node_modules/pi-mono-team-mode/core/tasks.ts +99 -0
- package/node_modules/pi-mono-team-mode/core/teammate-specs.ts +124 -0
- package/node_modules/pi-mono-team-mode/core/types.ts +160 -0
- package/node_modules/pi-mono-team-mode/index.ts +825 -0
- package/node_modules/pi-mono-team-mode/managers/agent-manager.ts +654 -0
- package/node_modules/pi-mono-team-mode/managers/delegation-manager.ts +211 -0
- package/node_modules/pi-mono-team-mode/managers/task-manager.ts +238 -0
- package/node_modules/pi-mono-team-mode/managers/team-manager.ts +59 -0
- package/node_modules/pi-mono-team-mode/package.json +33 -0
- package/node_modules/pi-mono-team-mode/runtime/pi-stream-parser.ts +194 -0
- package/node_modules/pi-mono-team-mode/runtime/subprocess.ts +183 -0
- package/node_modules/pi-mono-team-mode/runtime/transient-session.ts +196 -0
- package/node_modules/pi-mono-team-mode/runtime/worktree.ts +90 -0
- package/node_modules/pi-mono-team-mode/ui/formatters.ts +149 -0
- package/node_modules/pi-mono-team-mode/ui/notification-box.ts +55 -0
- package/node_modules/pi-mono-team-mode/ui/widget.ts +94 -0
- package/package.json +76 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-mono-ask-user-question",
|
|
3
|
+
"version": "1.7.3",
|
|
4
|
+
"description": "Pi extension for asking users structured interactive questions",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension"
|
|
8
|
+
],
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"@mariozechner/pi-ai": "*",
|
|
11
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
12
|
+
"@mariozechner/pi-tui": "*",
|
|
13
|
+
"@sinclair/typebox": "*"
|
|
14
|
+
},
|
|
15
|
+
"pi": {
|
|
16
|
+
"extensions": [
|
|
17
|
+
"./index.ts"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/emanuelcasco/pi-mono-extensions.git",
|
|
23
|
+
"directory": "extensions/ask-user-question"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/emanuelcasco/pi-mono-extensions/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/emanuelcasco/pi-mono-extensions#readme"
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# pi-mono-auto-fix
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **ESLint version-aware dispatch with config detection**: Auto-fix now determines the project's ESLint version and config format before running.
|
|
8
|
+
- Walks from the file's directory up to `package.json` to find the project root.
|
|
9
|
+
- Detects flat (`eslint.config.*`) vs. legacy (`.eslintrc.*`) configs.
|
|
10
|
+
- Reads the installed ESLint major version from `node_modules/eslint/package.json`.
|
|
11
|
+
| Local ESLint | Config Format | Action |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| v8 | Flat config | **Skip** — incompatible |
|
|
14
|
+
| v9+ | Legacy config | Use local + `ESLINT_USE_FLAT_CONFIG=false` |
|
|
15
|
+
| v8 | Legacy config | Use local binary directly |
|
|
16
|
+
| v9+ | Flat config | Use local binary directly |
|
|
17
|
+
| None | Legacy config | `npx --yes eslint@8` |
|
|
18
|
+
| None | Flat config | `npx --yes eslint@9` |
|
|
19
|
+
| None | No config | **Skip** — avoids injecting rules on projects that don't use ESLint |
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `resolveSpawnTarget` now returns `{ command, spawnCwd, env? }` so fixers can pass environment overrides (e.g. `ESLINT_USE_FLAT_CONFIG`).
|
|
24
|
+
- `runFixer` branches labelled `"eslint"` through the new `resolveEslintCommand` resolver.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Tightened `spawn` invocation typing for the release package without changing runtime behavior.
|
|
29
|
+
|
|
30
|
+
## 0.2.2
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **eslint failing on every file in projects pinned to v8**: The `0.2.1` neutral-cwd fix made `npx eslint` resolve from `/tmp`, which auto-installs the latest ESLint (v10+) and ignores the project's pinned version and config. Projects on ESLint v8 with `.eslintrc.*` then failed with "ESLint couldn't find an eslint.config.(js|mjs|cjs) file." on every file. Auto-fix now walks up from each file to find a project-local `node_modules/.bin/<tool>` and execs it directly with the project root as cwd, falling back to neutral-cwd npx only when no local install exists.
|
|
35
|
+
|
|
36
|
+
## 0.2.0
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **Auto-install dependencies**: Fixers now use `npx` (without `--no-install`) for eslint/prettier and `uvx` for ruff, auto-installing if missing — no manual install required.
|
|
41
|
+
- **False failure reporting with eslint**: `eslint --fix` exits non-zero when it finds unfixable issues, even if it successfully fixed others in the same pass. Failure detection now checks mtime (file actually changed) instead of exit code, so successful fixes are no longer reported as failures.
|
|
42
|
+
- **npx broken in pnpm monorepos**: `npx` delegates to pnpm when `package.json` declares `"packageManager": "pnpm"`, but `pnpm exec` doesn't auto-install like npx does. Fixers now use a neutral cwd (`/tmp`) for npx commands to bypass project-level toolchain interference.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Python fixer swapped from `black` (global install required) to `uvx ruff check --fix && uvx ruff format` (auto-installs via PyPI, faster).
|
|
47
|
+
|
|
48
|
+
## 0.1.0
|
|
49
|
+
|
|
50
|
+
### Minor Changes
|
|
51
|
+
|
|
52
|
+
### New Extension: auto-fix
|
|
53
|
+
|
|
54
|
+
End-of-turn formatter/linter dispatcher. Subscribes to `tool_result` (for `edit` / `write`) and the `context-guard:file-modified` event bus to collect every path written during a turn, then on `agent_end` routes each path to a language-appropriate fixer (eslint / black / prettier by default) and runs them in parallel. Re-emits `context-guard:file-modified` for files whose mtime actually changed so downstream read caches evict.
|
|
55
|
+
|
|
56
|
+
- Configurable via `~/.pi/agent/auto-fix.json` (fixers, ignore patterns, timeout, concurrency)
|
|
57
|
+
- Disable with `PI_AUTO_FIX=0`
|
|
58
|
+
- Silent stdout/stderr; single summary notification per flush
|
|
59
|
+
- Paths outside `ctx.cwd` and deleted files are skipped automatically
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# pi-mono-auto-fix
|
|
2
|
+
|
|
3
|
+
End-of-turn formatter/linter dispatcher for pi. Collects every file written during a turn and applies language-appropriate fixers (eslint, black, prettier, …) in one batch once the agent stops talking.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Subscribes to `tool_result` for the built-in `edit` and `write` tools, plus the `context-guard:file-modified` event that `multi-edit` and other writers emit.
|
|
8
|
+
- Buffers absolute paths of touched files in a per-turn `Set`.
|
|
9
|
+
- On `agent_end`, groups paths by matching fixer, runs each fixer once per group (parallel up to `concurrency`).
|
|
10
|
+
- After each run, re-emits `context-guard:file-modified` for any file whose mtime actually changed, so downstream read caches evict.
|
|
11
|
+
- Emits a single notification at the end (`auto-fix: N/M files updated`).
|
|
12
|
+
|
|
13
|
+
Fixers are invoked silently — stdout/stderr are swallowed. Failures are reported in the summary notification but never surfaced into the LLM context.
|
|
14
|
+
|
|
15
|
+
## Built-in fixer rules
|
|
16
|
+
|
|
17
|
+
| Extensions | Command |
|
|
18
|
+
| ------------------------------------------------- | -------------------------------------------------------------- |
|
|
19
|
+
| `.ts .tsx .js .jsx .mjs .cjs` | `npx --no-install eslint --fix --no-error-on-unmatched-pattern {files}` |
|
|
20
|
+
| `.py` | `black -q {files}` |
|
|
21
|
+
| `.json .md .yml .yaml .css .scss .html` | `npx --no-install prettier --write --log-level=warn {files}` |
|
|
22
|
+
|
|
23
|
+
`{files}` is replaced with shell-quoted, space-separated absolute paths. If the token is missing, files are appended to the end of the command.
|
|
24
|
+
|
|
25
|
+
Commands run with `shell: true`, `cwd = ctx.cwd`, and a per-invocation timeout (default 60s).
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Resolution order (first hit wins):
|
|
30
|
+
|
|
31
|
+
1. `PI_AUTO_FIX=0` → extension is fully disabled (no listeners registered)
|
|
32
|
+
2. `~/.pi/agent/auto-fix.json`
|
|
33
|
+
3. built-in defaults
|
|
34
|
+
|
|
35
|
+
Example `~/.pi/agent/auto-fix.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"timeoutMs": 90000,
|
|
41
|
+
"concurrency": 2,
|
|
42
|
+
"ignore": ["node_modules/", "dist/", ".git/", "vendor/"],
|
|
43
|
+
"fixers": [
|
|
44
|
+
{
|
|
45
|
+
"label": "biome",
|
|
46
|
+
"extensions": [".ts", ".tsx", ".js", ".jsx"],
|
|
47
|
+
"command": "npx --no-install biome check --write --no-errors-on-unmatched {files}"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"label": "ruff",
|
|
51
|
+
"extensions": [".py"],
|
|
52
|
+
"command": "ruff check --fix {files} && ruff format {files}"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
All fields are optional; anything omitted falls back to the built-in default.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pi install npm:pi-mono-auto-fix
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or load directly for testing:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pi -e /path/to/pi-extensions/extensions/auto-fix/index.ts
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- Paths outside `ctx.cwd` are skipped for safety.
|
|
75
|
+
- Paths matching any substring in `ignore` are skipped.
|
|
76
|
+
- Files deleted during the turn are skipped (existence is re-checked at flush time).
|
|
77
|
+
- The mtime diff catches fixers that are no-ops on already-clean files, so the summary reflects real changes rather than just invocations.
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-fix — end-of-turn formatter/linter dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* Collects every file written during a turn (via `edit` / `write` tool
|
|
5
|
+
* results and `context-guard:file-modified` events), then on `agent_end`
|
|
6
|
+
* dispatches each file to a language-appropriate fixer command (eslint,
|
|
7
|
+
* black, prettier, etc.). Fixes are applied silently; the user is only
|
|
8
|
+
* notified of the final summary.
|
|
9
|
+
*
|
|
10
|
+
* Config resolution order (first hit wins):
|
|
11
|
+
* 1. PI_AUTO_FIX=0 → extension is disabled entirely
|
|
12
|
+
* 2. ~/.pi/agent/auto-fix.json
|
|
13
|
+
* 3. built-in defaults (see DEFAULT_FIXERS below)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
ExtensionAPI,
|
|
18
|
+
ExtensionContext,
|
|
19
|
+
} from "@mariozechner/pi-coding-agent";
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
21
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Config
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
interface FixerRule {
|
|
30
|
+
/** File extensions this rule matches (include leading dot, lowercase). */
|
|
31
|
+
extensions: string[];
|
|
32
|
+
/** Shell command; `{files}` is replaced with space-separated, quoted paths. */
|
|
33
|
+
command: string;
|
|
34
|
+
/** Optional human-readable label used in notifications. */
|
|
35
|
+
label?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Config {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
fixers: FixerRule[];
|
|
41
|
+
/** Glob-ish substring ignore patterns applied to the relative path. */
|
|
42
|
+
ignore: string[];
|
|
43
|
+
/** Per-fixer timeout in ms. */
|
|
44
|
+
timeoutMs: number;
|
|
45
|
+
/** Max parallel fixer invocations. */
|
|
46
|
+
concurrency: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DEFAULT_FIXERS: FixerRule[] = [
|
|
50
|
+
{
|
|
51
|
+
label: "eslint",
|
|
52
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
53
|
+
command: "npx eslint --fix --no-error-on-unmatched-pattern {files}",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: "ruff",
|
|
57
|
+
extensions: [".py"],
|
|
58
|
+
command:
|
|
59
|
+
"uvx ruff check --fix --quiet {files} && uvx ruff format --quiet {files}",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: "prettier",
|
|
63
|
+
extensions: [".json", ".md", ".yml", ".yaml", ".css", ".scss", ".html"],
|
|
64
|
+
command: "npx prettier --write --log-level=warn {files}",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const DEFAULT_CONFIG: Config = {
|
|
69
|
+
enabled: true,
|
|
70
|
+
fixers: DEFAULT_FIXERS,
|
|
71
|
+
ignore: ["node_modules/", "dist/", "build/", ".git/", ".next/", "coverage/"],
|
|
72
|
+
timeoutMs: 60_000,
|
|
73
|
+
concurrency: 3,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const CONFIG_PATH = `${homedir()}/.pi/agent/auto-fix.json`;
|
|
77
|
+
|
|
78
|
+
function loadConfig(): Config {
|
|
79
|
+
if (process.env.PI_AUTO_FIX === "0") {
|
|
80
|
+
return { ...DEFAULT_CONFIG, enabled: false };
|
|
81
|
+
}
|
|
82
|
+
if (!existsSync(CONFIG_PATH)) return DEFAULT_CONFIG;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(
|
|
85
|
+
readFileSync(CONFIG_PATH, "utf-8"),
|
|
86
|
+
) as Partial<Config>;
|
|
87
|
+
return {
|
|
88
|
+
enabled: parsed.enabled ?? DEFAULT_CONFIG.enabled,
|
|
89
|
+
fixers: parsed.fixers ?? DEFAULT_CONFIG.fixers,
|
|
90
|
+
ignore: parsed.ignore ?? DEFAULT_CONFIG.ignore,
|
|
91
|
+
timeoutMs: parsed.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
|
|
92
|
+
concurrency: parsed.concurrency ?? DEFAULT_CONFIG.concurrency,
|
|
93
|
+
};
|
|
94
|
+
} catch {
|
|
95
|
+
return DEFAULT_CONFIG;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Helpers
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
function shellQuote(s: string): string {
|
|
104
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function matchFixer(
|
|
108
|
+
absPath: string,
|
|
109
|
+
fixers: FixerRule[],
|
|
110
|
+
): FixerRule | undefined {
|
|
111
|
+
const ext = extname(absPath).toLowerCase();
|
|
112
|
+
if (!ext) return undefined;
|
|
113
|
+
return fixers.find((f) => f.extensions.includes(ext));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isIgnored(relPath: string, ignore: string[]): boolean {
|
|
117
|
+
return ignore.some((p) => relPath.includes(p));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function mtimeSafe(path: string): number {
|
|
121
|
+
try {
|
|
122
|
+
return statSync(path).mtimeMs;
|
|
123
|
+
} catch {
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Walk up from `startDir` until a directory containing `node_modules/.bin/<tool>`
|
|
130
|
+
* is found. Returns `{ binPath, projectRoot }` or undefined.
|
|
131
|
+
*/
|
|
132
|
+
function findLocalBin(
|
|
133
|
+
startDir: string,
|
|
134
|
+
tool: string,
|
|
135
|
+
): { binPath: string; projectRoot: string } | undefined {
|
|
136
|
+
let dir = startDir;
|
|
137
|
+
while (true) {
|
|
138
|
+
const binPath = `${dir}/node_modules/.bin/${tool}`;
|
|
139
|
+
if (existsSync(binPath)) return { binPath, projectRoot: dir };
|
|
140
|
+
const parent = dirname(dir);
|
|
141
|
+
if (parent === dir) return undefined;
|
|
142
|
+
dir = parent;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Walk up from `startDir` until a `package.json` is found.
|
|
148
|
+
*/
|
|
149
|
+
function findProjectRoot(startDir: string): string | undefined {
|
|
150
|
+
let dir = startDir;
|
|
151
|
+
while (true) {
|
|
152
|
+
if (existsSync(`${dir}/package.json`)) return dir;
|
|
153
|
+
const parent = dirname(dir);
|
|
154
|
+
if (parent === dir) return undefined;
|
|
155
|
+
dir = parent;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const ESLINT_FLAT_CONFIG_FILES = [
|
|
160
|
+
"eslint.config.mjs",
|
|
161
|
+
"eslint.config.js",
|
|
162
|
+
"eslint.config.cjs",
|
|
163
|
+
"eslint.config.ts",
|
|
164
|
+
"eslint.config.mts",
|
|
165
|
+
"eslint.config.cts",
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const ESLINT_LEGACY_CONFIG_FILES = [
|
|
169
|
+
".eslintrc.cjs",
|
|
170
|
+
".eslintrc.mjs",
|
|
171
|
+
".eslintrc.js",
|
|
172
|
+
".eslintrc.json",
|
|
173
|
+
".eslintrc.yaml",
|
|
174
|
+
".eslintrc.yml",
|
|
175
|
+
".eslintrc",
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
function detectEslintConfigType(projectRoot: string): "flat" | "legacy" | "none" {
|
|
179
|
+
for (const f of ESLINT_FLAT_CONFIG_FILES) {
|
|
180
|
+
if (existsSync(`${projectRoot}/${f}`)) return "flat";
|
|
181
|
+
}
|
|
182
|
+
for (const f of ESLINT_LEGACY_CONFIG_FILES) {
|
|
183
|
+
if (existsSync(`${projectRoot}/${f}`)) return "legacy";
|
|
184
|
+
}
|
|
185
|
+
return "none";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getEslintVersion(projectRoot: string): number | null {
|
|
189
|
+
const pkgPath = `${projectRoot}/node_modules/eslint/package.json`;
|
|
190
|
+
if (!existsSync(pkgPath)) return null;
|
|
191
|
+
try {
|
|
192
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
193
|
+
const major = parseInt(pkg.version.split(".")[0], 10);
|
|
194
|
+
return isNaN(major) ? null : major;
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
interface SpawnTarget {
|
|
201
|
+
command: string | null;
|
|
202
|
+
spawnCwd: string;
|
|
203
|
+
env?: Record<string, string>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* ESLint-specific resolver that avoids version / config mismatches.
|
|
208
|
+
*
|
|
209
|
+
* Logic:
|
|
210
|
+
* 1. Local ESLint installed?
|
|
211
|
+
* a. v9+ + legacy config → use local + ESLINT_USE_FLAT_CONFIG=false
|
|
212
|
+
* b. v8 + flat config → skip (incompatible)
|
|
213
|
+
* c. otherwise → use local binary directly
|
|
214
|
+
* 2. No local ESLint?
|
|
215
|
+
* a. legacy config → `npx --yes eslint@8` (pin v8)
|
|
216
|
+
* b. flat config → `npx --yes eslint@9` (pin v9+)
|
|
217
|
+
* c. no config → skip
|
|
218
|
+
*/
|
|
219
|
+
function resolveEslintCommand(
|
|
220
|
+
commandTemplate: string,
|
|
221
|
+
files: string[],
|
|
222
|
+
cwd: string,
|
|
223
|
+
): SpawnTarget {
|
|
224
|
+
const firstFile = files[0];
|
|
225
|
+
const fileDir = firstFile ? dirname(firstFile) : cwd;
|
|
226
|
+
const projectRoot = findProjectRoot(fileDir) ?? cwd;
|
|
227
|
+
|
|
228
|
+
const local = findLocalBin(fileDir, "eslint");
|
|
229
|
+
const configType = detectEslintConfigType(projectRoot);
|
|
230
|
+
const filesArg = files.map(shellQuote).join(" ");
|
|
231
|
+
|
|
232
|
+
// Strip the binary invocation and {files} placeholder to keep only flags.
|
|
233
|
+
const extraArgs = commandTemplate
|
|
234
|
+
.replace(/^npx\s+/, "")
|
|
235
|
+
.replace(/^(?:[.\\/][\S]+[\\/])?eslint(?:\.exe)?\b\s*/, "")
|
|
236
|
+
.replace("{files}", "")
|
|
237
|
+
.trim();
|
|
238
|
+
|
|
239
|
+
// CASE 1: Local ESLint installed — honour project's pinned version.
|
|
240
|
+
if (local) {
|
|
241
|
+
const version = getEslintVersion(local.projectRoot);
|
|
242
|
+
|
|
243
|
+
// ESLint v9+ on a legacy-config project: force legacy mode so the v9
|
|
244
|
+
// binary can still read .eslintrc.* files.
|
|
245
|
+
if (version !== null && version >= 9 && configType === "legacy") {
|
|
246
|
+
return {
|
|
247
|
+
command: `${shellQuote(local.binPath)} ${extraArgs} ${filesArg}`.trim(),
|
|
248
|
+
spawnCwd: local.projectRoot,
|
|
249
|
+
env: { ESLINT_USE_FLAT_CONFIG: "false" },
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ESLint v8 on a flat-config project: incompatible — skip.
|
|
254
|
+
if (version !== null && version < 9 && configType === "flat") {
|
|
255
|
+
return { command: null, spawnCwd: local.projectRoot };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
command: `${shellQuote(local.binPath)} ${extraArgs} ${filesArg}`.trim(),
|
|
260
|
+
spawnCwd: local.projectRoot,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// CASE 2: No local ESLint — pin a version compatible with the project's
|
|
265
|
+
// config format so we never install latest v9 on a legacy-config repo.
|
|
266
|
+
if (configType === "legacy") {
|
|
267
|
+
return {
|
|
268
|
+
command: `npx --yes eslint@8 ${extraArgs} ${filesArg}`.trim(),
|
|
269
|
+
spawnCwd: projectRoot,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (configType === "flat") {
|
|
274
|
+
return {
|
|
275
|
+
command: `npx --yes eslint@9 ${extraArgs} ${filesArg}`.trim(),
|
|
276
|
+
spawnCwd: projectRoot,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// CASE 3: No ESLint config found — skip to avoid injecting unwanted rules.
|
|
281
|
+
return { command: null, spawnCwd: projectRoot };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* If `command` starts with `npx <tool>`, attempt to resolve a project-local
|
|
286
|
+
* binary by walking up from the first file. On hit, rewrite the command to
|
|
287
|
+
* exec the local bin directly and return the project root as cwd. On miss,
|
|
288
|
+
* fall back to `/tmp` so npx can fetch the latest from the registry without
|
|
289
|
+
* being delegated through pnpm.
|
|
290
|
+
*/
|
|
291
|
+
function resolveSpawnTarget(
|
|
292
|
+
command: string,
|
|
293
|
+
files: string[],
|
|
294
|
+
cwd: string,
|
|
295
|
+
): SpawnTarget {
|
|
296
|
+
const npxMatch = command.match(/^npx\s+(\S+)/);
|
|
297
|
+
if (!npxMatch) return { command, spawnCwd: cwd };
|
|
298
|
+
|
|
299
|
+
const tool = npxMatch[1];
|
|
300
|
+
const firstFile = files[0];
|
|
301
|
+
if (firstFile) {
|
|
302
|
+
const local = findLocalBin(dirname(firstFile), tool);
|
|
303
|
+
if (local) {
|
|
304
|
+
return {
|
|
305
|
+
command: command.replace(/^npx\s+\S+/, shellQuote(local.binPath)),
|
|
306
|
+
spawnCwd: local.projectRoot,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// No local install — neutral cwd avoids pnpm delegating npx.
|
|
311
|
+
return { command, spawnCwd: "/tmp" };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function runFixer(
|
|
315
|
+
rule: FixerRule,
|
|
316
|
+
files: string[],
|
|
317
|
+
cwd: string,
|
|
318
|
+
timeoutMs: number,
|
|
319
|
+
): Promise<{ ok: boolean; stderr: string }> {
|
|
320
|
+
const filesArg = files.map(shellQuote).join(" ");
|
|
321
|
+
|
|
322
|
+
let resolved: SpawnTarget;
|
|
323
|
+
|
|
324
|
+
if (rule.label === "eslint") {
|
|
325
|
+
// ESLint gets version-aware command resolution so we don't install a
|
|
326
|
+
// global v9 on a legacy v8-configured project (or vice-versa).
|
|
327
|
+
resolved = resolveEslintCommand(rule.command, files, cwd);
|
|
328
|
+
} else {
|
|
329
|
+
const rawCommand = rule.command.includes("{files}")
|
|
330
|
+
? rule.command.replace("{files}", filesArg)
|
|
331
|
+
: `${rule.command} ${filesArg}`;
|
|
332
|
+
// Prefer the project's locally installed binary when available so we
|
|
333
|
+
// honor the pinned version + config. Only fall back to neutral-cwd npx
|
|
334
|
+
// when no local install is found, which sidesteps pnpm's delegation of
|
|
335
|
+
// npx without auto-install.
|
|
336
|
+
resolved = resolveSpawnTarget(rawCommand, files, cwd);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const command = resolved.command;
|
|
340
|
+
if (command === null) {
|
|
341
|
+
return {
|
|
342
|
+
ok: true,
|
|
343
|
+
stderr: `Skipped ${rule.label ?? rule.command}: no compatible target found.`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return new Promise((resolvePromise) => {
|
|
348
|
+
const child = spawn(command, [], {
|
|
349
|
+
cwd: resolved.spawnCwd,
|
|
350
|
+
shell: true,
|
|
351
|
+
env: { ...process.env, ...resolved.env },
|
|
352
|
+
});
|
|
353
|
+
let stderr = "";
|
|
354
|
+
const timer = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
|
|
355
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
356
|
+
stderr += chunk.toString();
|
|
357
|
+
});
|
|
358
|
+
child.on("error", () => {
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
resolvePromise({ ok: false, stderr });
|
|
361
|
+
});
|
|
362
|
+
child.on("close", (code: number | null) => {
|
|
363
|
+
clearTimeout(timer);
|
|
364
|
+
resolvePromise({ ok: code === 0, stderr });
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function runWithConcurrency<T>(
|
|
370
|
+
items: T[],
|
|
371
|
+
limit: number,
|
|
372
|
+
worker: (item: T) => Promise<void>,
|
|
373
|
+
): Promise<void> {
|
|
374
|
+
const queue = [...items];
|
|
375
|
+
const workers = Array.from(
|
|
376
|
+
{ length: Math.min(limit, queue.length) },
|
|
377
|
+
async () => {
|
|
378
|
+
while (queue.length) {
|
|
379
|
+
const item = queue.shift();
|
|
380
|
+
if (item === undefined) return;
|
|
381
|
+
await worker(item);
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
await Promise.all(workers);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
// Extension
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
export default function (pi: ExtensionAPI): void {
|
|
393
|
+
const cfg = loadConfig();
|
|
394
|
+
if (!cfg.enabled) return;
|
|
395
|
+
|
|
396
|
+
/** Absolute paths written during the current turn. */
|
|
397
|
+
const pending = new Set<string>();
|
|
398
|
+
|
|
399
|
+
function collect(rawPath: string, cwd: string): void {
|
|
400
|
+
if (!rawPath) return;
|
|
401
|
+
const absolutePath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
|
|
402
|
+
const rel = relative(cwd, absolutePath);
|
|
403
|
+
if (rel.startsWith("..")) return; // outside cwd — skip
|
|
404
|
+
if (isIgnored(rel, cfg.ignore)) return;
|
|
405
|
+
pending.add(absolutePath);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Reset between agent runs (turn boundary for pi's purposes).
|
|
409
|
+
pi.on("agent_start", () => {
|
|
410
|
+
pending.clear();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Collector 1: direct edit/write tool results.
|
|
414
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
415
|
+
if (event.isError) return;
|
|
416
|
+
if (event.toolName !== "edit" && event.toolName !== "write") return;
|
|
417
|
+
const rawPath = (event.input as { path?: string }).path;
|
|
418
|
+
if (rawPath) collect(rawPath, ctx.cwd);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// End-of-turn flush.
|
|
422
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
423
|
+
if (!pending.size) return;
|
|
424
|
+
const paths = [...pending];
|
|
425
|
+
pending.clear();
|
|
426
|
+
|
|
427
|
+
await flush(paths, ctx);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
async function flush(paths: string[], ctx: ExtensionContext): Promise<void> {
|
|
431
|
+
// Filter to existing files and group by fixer rule.
|
|
432
|
+
const groups = new Map<FixerRule, string[]>();
|
|
433
|
+
for (const p of paths) {
|
|
434
|
+
if (!existsSync(p)) continue;
|
|
435
|
+
const rule = matchFixer(p, cfg.fixers);
|
|
436
|
+
if (!rule) continue;
|
|
437
|
+
const bucket = groups.get(rule) ?? [];
|
|
438
|
+
bucket.push(p);
|
|
439
|
+
groups.set(rule, bucket);
|
|
440
|
+
}
|
|
441
|
+
if (!groups.size) return;
|
|
442
|
+
|
|
443
|
+
let changed = 0;
|
|
444
|
+
const failures: string[] = [];
|
|
445
|
+
const jobs = [...groups.entries()];
|
|
446
|
+
|
|
447
|
+
await runWithConcurrency(jobs, cfg.concurrency, async ([rule, files]) => {
|
|
448
|
+
// Snapshot mtimes *before* running this fixer group.
|
|
449
|
+
const groupBefore = new Map(files.map((p) => [p, mtimeSafe(p)]));
|
|
450
|
+
|
|
451
|
+
const result = await runFixer(rule, files, ctx.cwd, cfg.timeoutMs);
|
|
452
|
+
|
|
453
|
+
// Count how many files this fixer actually changed.
|
|
454
|
+
const groupChanged = files.filter(
|
|
455
|
+
(p) => mtimeSafe(p) !== groupBefore.get(p),
|
|
456
|
+
).length;
|
|
457
|
+
changed += groupChanged;
|
|
458
|
+
|
|
459
|
+
// Only count as failure if the tool failed AND no files changed.
|
|
460
|
+
// Some tools (eslint --fix) exit non-zero even when they fix things.
|
|
461
|
+
if (!result.ok && groupChanged === 0) {
|
|
462
|
+
failures.push(
|
|
463
|
+
`${rule.label ?? rule.command.split(" ")[0]} (${files.length} file${files.length === 1 ? "" : "s"})`,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Re-emit file-modified for anything the fixer actually rewrote so
|
|
468
|
+
// context-guard evicts its stale read cache.
|
|
469
|
+
for (const p of files) {
|
|
470
|
+
if (mtimeSafe(p) !== groupBefore.get(p)) {
|
|
471
|
+
pi.events.emit("context-guard:file-modified", { path: p });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const total = [...groups.values()].reduce((n, b) => n + b.length, 0);
|
|
477
|
+
if (changed > 0 || failures.length) {
|
|
478
|
+
const parts: string[] = [
|
|
479
|
+
`auto-fix: ${changed}/${total} file${total === 1 ? "" : "s"} updated`,
|
|
480
|
+
];
|
|
481
|
+
if (failures.length) parts.push(`failed: ${failures.join(", ")}`);
|
|
482
|
+
ctx.ui.notify(
|
|
483
|
+
`[auto-fix] ${parts.join(" — ")}`,
|
|
484
|
+
failures.length ? "warning" : "info",
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-mono-auto-fix",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Pi extension that runs language-appropriate fixers (eslint, black, prettier, ...) on files touched during a turn",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension"
|
|
8
|
+
],
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
11
|
+
"@sinclair/typebox": "*"
|
|
12
|
+
},
|
|
13
|
+
"pi": {
|
|
14
|
+
"extensions": [
|
|
15
|
+
"./index.ts"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/emanuelcasco/pi-mono-extensions.git",
|
|
21
|
+
"directory": "extensions/auto-fix"
|
|
22
|
+
}
|
|
23
|
+
}
|