pi-formatter 0.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/DOCUMENTATION.md +120 -0
- package/LICENSE +191 -0
- package/README.md +41 -0
- package/extensions/format/config.ts +11 -0
- package/extensions/format/context.ts +277 -0
- package/extensions/format/dispatch.ts +237 -0
- package/extensions/format/path.ts +89 -0
- package/extensions/format/plan.ts +26 -0
- package/extensions/format/runners/biome-check-write.ts +11 -0
- package/extensions/format/runners/clang-format.ts +27 -0
- package/extensions/format/runners/cmake-format.ts +9 -0
- package/extensions/format/runners/config-patterns.ts +6 -0
- package/extensions/format/runners/eslint-fix.ts +11 -0
- package/extensions/format/runners/helpers.ts +22 -0
- package/extensions/format/runners/index.ts +42 -0
- package/extensions/format/runners/markdownlint-fix.ts +9 -0
- package/extensions/format/runners/prettier-config-write.ts +11 -0
- package/extensions/format/runners/prettier-markdown.ts +9 -0
- package/extensions/format/runners/ruff-check-fix.ts +9 -0
- package/extensions/format/runners/ruff-format.ts +9 -0
- package/extensions/format/runners/shfmt.ts +19 -0
- package/extensions/format/system.ts +57 -0
- package/extensions/format/types.ts +112 -0
- package/extensions/index.ts +65 -0
- package/package.json +40 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ExecResult } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export type SourceTool = "write" | "edit";
|
|
4
|
+
|
|
5
|
+
export type FileKind =
|
|
6
|
+
| "cxx"
|
|
7
|
+
| "cmake"
|
|
8
|
+
| "markdown"
|
|
9
|
+
| "json"
|
|
10
|
+
| "shell"
|
|
11
|
+
| "python"
|
|
12
|
+
| "jsts";
|
|
13
|
+
|
|
14
|
+
export type RunnerMode = "all" | "fallback";
|
|
15
|
+
export type RequiredMajorVersion = "invalid" | string | undefined;
|
|
16
|
+
|
|
17
|
+
export interface DirectLauncher {
|
|
18
|
+
type: "direct";
|
|
19
|
+
command: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PypiLauncher {
|
|
23
|
+
type: "pypi";
|
|
24
|
+
tool: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GoLauncher {
|
|
28
|
+
type: "go";
|
|
29
|
+
tool: string;
|
|
30
|
+
module: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type RunnerLauncher = DirectLauncher | PypiLauncher | GoLauncher;
|
|
34
|
+
|
|
35
|
+
export interface ResolvedLauncher {
|
|
36
|
+
command: string;
|
|
37
|
+
argsPrefix: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RunnerContext {
|
|
41
|
+
readonly filePath: string;
|
|
42
|
+
readonly cwd: string;
|
|
43
|
+
readonly sourceTool: SourceTool;
|
|
44
|
+
readonly kind: FileKind;
|
|
45
|
+
|
|
46
|
+
hasCommand(command: string): Promise<boolean>;
|
|
47
|
+
hasConfig(patterns: readonly string[]): Promise<boolean>;
|
|
48
|
+
findConfigFile(patterns: readonly string[]): Promise<string | undefined>;
|
|
49
|
+
hasEditorConfigInCwd(): Promise<boolean>;
|
|
50
|
+
|
|
51
|
+
exec(command: string, args: string[]): Promise<ExecResult | undefined>;
|
|
52
|
+
getChangedLines(): Promise<string[]>;
|
|
53
|
+
getRequiredMajorVersionFromConfig(
|
|
54
|
+
patterns: readonly string[],
|
|
55
|
+
): Promise<RequiredMajorVersion>;
|
|
56
|
+
getInstalledToolMajorVersion(command: string): Promise<string | undefined>;
|
|
57
|
+
|
|
58
|
+
warn(message: string): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type RunnerPredicate =
|
|
62
|
+
| ((ctx: RunnerContext) => Promise<boolean> | boolean)
|
|
63
|
+
| undefined;
|
|
64
|
+
|
|
65
|
+
export interface MajorVersionFromConfigRequirement {
|
|
66
|
+
patterns: readonly string[];
|
|
67
|
+
/**
|
|
68
|
+
* Optional command used for <cmd> --version lookup.
|
|
69
|
+
* Defaults to the command/tool declared in launcher.
|
|
70
|
+
*/
|
|
71
|
+
command?: string;
|
|
72
|
+
onInvalid?: "warn-skip" | "skip";
|
|
73
|
+
onMismatch?: "warn-skip" | "skip";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RunnerRequirements {
|
|
77
|
+
majorVersionFromConfig?: MajorVersionFromConfigRequirement;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface RunnerBase {
|
|
81
|
+
id: string;
|
|
82
|
+
launcher: RunnerLauncher;
|
|
83
|
+
when?: RunnerPredicate;
|
|
84
|
+
requires?: RunnerRequirements;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface StaticRunnerDefinition extends RunnerBase {
|
|
88
|
+
args: string[];
|
|
89
|
+
/**
|
|
90
|
+
* Whether to append the target file path as the final argument.
|
|
91
|
+
* Defaults to true.
|
|
92
|
+
*/
|
|
93
|
+
appendFile?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface DynamicRunnerDefinition extends RunnerBase {
|
|
97
|
+
buildArgs:
|
|
98
|
+
(ctx: RunnerContext) => Promise<string[] | undefined> | string[] | undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type RunnerDefinition = StaticRunnerDefinition | DynamicRunnerDefinition;
|
|
102
|
+
|
|
103
|
+
export interface RunnerGroup {
|
|
104
|
+
mode: RunnerMode;
|
|
105
|
+
runnerIds: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isDynamicRunner(
|
|
109
|
+
runner: RunnerDefinition,
|
|
110
|
+
): runner is DynamicRunnerDefinition {
|
|
111
|
+
return "buildArgs" in runner;
|
|
112
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { commandTimeoutMs } from "./format/config.js";
|
|
3
|
+
import { formatFile } from "./format/dispatch.js";
|
|
4
|
+
import { pathExists, resolveToolPath } from "./format/path.js";
|
|
5
|
+
import type { SourceTool } from "./format/types.js";
|
|
6
|
+
|
|
7
|
+
function formatError(error: unknown): string {
|
|
8
|
+
return error instanceof Error ? error.message : String(error);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function (pi: ExtensionAPI) {
|
|
12
|
+
const formatQueueByPath = new Map<string, Promise<void>>();
|
|
13
|
+
|
|
14
|
+
const enqueueFormat = async (
|
|
15
|
+
filePath: string,
|
|
16
|
+
run: () => Promise<void>,
|
|
17
|
+
): Promise<void> => {
|
|
18
|
+
const previous = formatQueueByPath.get(filePath) ?? Promise.resolve();
|
|
19
|
+
const next = previous
|
|
20
|
+
.catch(() => {
|
|
21
|
+
// Keep the queue alive after a failure.
|
|
22
|
+
})
|
|
23
|
+
.then(run)
|
|
24
|
+
.finally(() => {
|
|
25
|
+
if (formatQueueByPath.get(filePath) === next) {
|
|
26
|
+
formatQueueByPath.delete(filePath);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
formatQueueByPath.set(filePath, next);
|
|
31
|
+
await next;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
35
|
+
if (event.isError) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (event.toolName !== "write" && event.toolName !== "edit") {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rawPath = (event.input as { path?: unknown }).path;
|
|
44
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sourceTool = event.toolName as SourceTool;
|
|
49
|
+
const filePath = resolveToolPath(rawPath, ctx.cwd);
|
|
50
|
+
|
|
51
|
+
if (!(await pathExists(filePath))) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await enqueueFormat(filePath, async () => {
|
|
56
|
+
try {
|
|
57
|
+
await formatFile(pi, ctx.cwd, sourceTool, filePath, commandTimeoutMs);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn(
|
|
60
|
+
`[pi-formatter] Failed to format ${filePath}: ${formatError(error)}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-formatter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pi extension that auto-formats files after write/edit tool calls.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"extensions",
|
|
8
|
+
"DOCUMENTATION.md",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"pi-package",
|
|
14
|
+
"pi-extension",
|
|
15
|
+
"coding-agent",
|
|
16
|
+
"formatter",
|
|
17
|
+
"formatting"
|
|
18
|
+
],
|
|
19
|
+
"author": "Tenzir",
|
|
20
|
+
"license": "Apache-2.0",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/tenzir/pi-formatter.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/tenzir/pi-formatter#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/tenzir/pi-formatter/issues"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"pi": {
|
|
36
|
+
"extensions": [
|
|
37
|
+
"./extensions/index.ts"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|