pi-rtk-optimizer 0.3.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.
@@ -0,0 +1,230 @@
1
+ export type Language =
2
+ | "typescript"
3
+ | "javascript"
4
+ | "python"
5
+ | "rust"
6
+ | "go"
7
+ | "java"
8
+ | "c"
9
+ | "cpp"
10
+ | "unknown";
11
+
12
+ const LANGUAGE_EXTENSIONS: Record<string, Language> = {
13
+ ".ts": "typescript",
14
+ ".tsx": "typescript",
15
+ ".js": "javascript",
16
+ ".jsx": "javascript",
17
+ ".mjs": "javascript",
18
+ ".py": "python",
19
+ ".pyw": "python",
20
+ ".rs": "rust",
21
+ ".go": "go",
22
+ ".java": "java",
23
+ ".c": "c",
24
+ ".h": "c",
25
+ ".cpp": "cpp",
26
+ ".hpp": "cpp",
27
+ ".cc": "cpp",
28
+ };
29
+
30
+ interface CommentPatterns {
31
+ line?: string;
32
+ blockStart?: string;
33
+ blockEnd?: string;
34
+ docLine?: string;
35
+ docBlockStart?: string;
36
+ }
37
+
38
+ const COMMENT_PATTERNS: Record<Language, CommentPatterns> = {
39
+ typescript: { line: "//", blockStart: "/*", blockEnd: "*/", docBlockStart: "/**" },
40
+ javascript: { line: "//", blockStart: "/*", blockEnd: "*/", docBlockStart: "/**" },
41
+ python: { line: "#", blockStart: '"""', blockEnd: '"""', docBlockStart: '"""' },
42
+ rust: { line: "//", blockStart: "/*", blockEnd: "*/", docLine: "///", docBlockStart: "/**" },
43
+ go: { line: "//", blockStart: "/*", blockEnd: "*/", docBlockStart: "/**" },
44
+ java: { line: "//", blockStart: "/*", blockEnd: "*/", docBlockStart: "/**" },
45
+ c: { line: "//", blockStart: "/*", blockEnd: "*/", docBlockStart: "/**" },
46
+ cpp: { line: "//", blockStart: "/*", blockEnd: "*/", docBlockStart: "/**" },
47
+ unknown: { line: "//", blockStart: "/*", blockEnd: "*/" },
48
+ };
49
+
50
+ const IMPORT_PATTERN = /^(use\s+|import\s+|from\s+|require\(|#include)/;
51
+ const SIGNATURE_PATTERN = /^(pub\s+)?(async\s+)?(fn|def|function|func|class|struct|enum|trait|interface|type)\s+\w+/;
52
+ const CONST_PATTERN = /^(const|static|let|pub\s+const|pub\s+static)\s+/;
53
+
54
+ export function detectLanguage(filePath: string): Language {
55
+ const lastDot = filePath.lastIndexOf(".");
56
+ if (lastDot === -1) {
57
+ return "unknown";
58
+ }
59
+ const extension = filePath.slice(lastDot).toLowerCase();
60
+ return LANGUAGE_EXTENSIONS[extension] ?? "unknown";
61
+ }
62
+
63
+ export function filterMinimal(content: string, language: Language): string {
64
+ const patterns = COMMENT_PATTERNS[language];
65
+ const lines = content.split("\n");
66
+ const result: string[] = [];
67
+ let inBlockComment = false;
68
+ let inDocstring = false;
69
+
70
+ for (const line of lines) {
71
+ const trimmed = line.trim();
72
+
73
+ if (patterns.blockStart && patterns.blockEnd) {
74
+ if (
75
+ !inDocstring &&
76
+ trimmed.includes(patterns.blockStart) &&
77
+ !(patterns.docBlockStart && trimmed.startsWith(patterns.docBlockStart))
78
+ ) {
79
+ inBlockComment = true;
80
+ }
81
+
82
+ if (inBlockComment) {
83
+ if (trimmed.includes(patterns.blockEnd)) {
84
+ inBlockComment = false;
85
+ }
86
+ continue;
87
+ }
88
+ }
89
+
90
+ if (language === "python" && trimmed.startsWith('"""')) {
91
+ inDocstring = !inDocstring;
92
+ result.push(line);
93
+ continue;
94
+ }
95
+
96
+ if (inDocstring) {
97
+ result.push(line);
98
+ continue;
99
+ }
100
+
101
+ if (patterns.line && trimmed.startsWith(patterns.line)) {
102
+ if (patterns.docLine && trimmed.startsWith(patterns.docLine)) {
103
+ result.push(line);
104
+ }
105
+ continue;
106
+ }
107
+
108
+ if (trimmed.length === 0) {
109
+ result.push("");
110
+ continue;
111
+ }
112
+
113
+ result.push(line);
114
+ }
115
+
116
+ return result
117
+ .join("\n")
118
+ .replace(/\n{3,}/g, "\n\n")
119
+ .trim();
120
+ }
121
+
122
+ export function filterAggressive(content: string, language: Language): string {
123
+ const minimal = filterMinimal(content, language);
124
+ const lines = minimal.split("\n");
125
+ const result: string[] = [];
126
+ let braceDepth = 0;
127
+ let inImplementation = false;
128
+
129
+ for (const line of lines) {
130
+ const trimmed = line.trim();
131
+
132
+ if (IMPORT_PATTERN.test(trimmed)) {
133
+ result.push(line);
134
+ continue;
135
+ }
136
+
137
+ if (SIGNATURE_PATTERN.test(trimmed)) {
138
+ result.push(line);
139
+ inImplementation = true;
140
+ braceDepth = 0;
141
+ continue;
142
+ }
143
+
144
+ const openBraces = (trimmed.match(/\{/g) ?? []).length;
145
+ const closeBraces = (trimmed.match(/\}/g) ?? []).length;
146
+
147
+ if (inImplementation) {
148
+ braceDepth += openBraces;
149
+ braceDepth -= closeBraces;
150
+
151
+ if (braceDepth <= 1 && (trimmed === "{" || trimmed === "}" || trimmed.endsWith("{"))) {
152
+ result.push(line);
153
+ }
154
+
155
+ if (braceDepth <= 0) {
156
+ inImplementation = false;
157
+ if (trimmed.length > 0 && trimmed !== "}") {
158
+ result.push(" // ... implementation");
159
+ }
160
+ }
161
+ continue;
162
+ }
163
+
164
+ if (CONST_PATTERN.test(trimmed)) {
165
+ result.push(line);
166
+ }
167
+ }
168
+
169
+ return result.join("\n").trim();
170
+ }
171
+
172
+ export function smartTruncate(content: string, maxLines: number, _language: Language): string {
173
+ const lines = content.split("\n");
174
+ if (lines.length <= maxLines) {
175
+ return content;
176
+ }
177
+
178
+ const result: string[] = [];
179
+ let keptLines = 0;
180
+ let skippedSection = false;
181
+
182
+ for (const line of lines) {
183
+ const trimmed = line.trim();
184
+ const isImportant =
185
+ SIGNATURE_PATTERN.test(trimmed) ||
186
+ IMPORT_PATTERN.test(trimmed) ||
187
+ trimmed.startsWith("pub ") ||
188
+ trimmed.startsWith("export ") ||
189
+ trimmed === "}" ||
190
+ trimmed === "{";
191
+
192
+ if (isImportant || keptLines < maxLines / 2) {
193
+ if (skippedSection) {
194
+ result.push(` // ... ${lines.length - keptLines} lines omitted`);
195
+ skippedSection = false;
196
+ }
197
+ result.push(line);
198
+ keptLines += 1;
199
+ } else {
200
+ skippedSection = true;
201
+ }
202
+
203
+ if (keptLines >= maxLines - 1) {
204
+ break;
205
+ }
206
+ }
207
+
208
+ if (skippedSection || keptLines < lines.length) {
209
+ result.push(`// ... ${lines.length - keptLines} more lines (total: ${lines.length})`);
210
+ }
211
+
212
+ return result.join("\n");
213
+ }
214
+
215
+ export function filterSourceCode(
216
+ content: string,
217
+ language: Language,
218
+ level: "none" | "minimal" | "aggressive",
219
+ ): string {
220
+ switch (level) {
221
+ case "none":
222
+ return content;
223
+ case "minimal":
224
+ return filterMinimal(content, language);
225
+ case "aggressive":
226
+ return filterAggressive(content, language);
227
+ default:
228
+ return content;
229
+ }
230
+ }
@@ -0,0 +1,172 @@
1
+ import { matchesCommandPatterns } from "./command-detection.js";
2
+
3
+ interface TestSummary {
4
+ passed: number;
5
+ failed: number;
6
+ skipped: number;
7
+ failures: string[];
8
+ }
9
+
10
+ const TEST_COMMAND_PATTERNS = [
11
+ /^npm\s+test\b/,
12
+ /^pnpm\s+test\b/,
13
+ /^yarn\s+test\b/,
14
+ /^bun\s+test\b/,
15
+ /^cargo\s+test\b/,
16
+ /^go\s+test\b/,
17
+ /^pytest\b/,
18
+ /^python\s+-m\s+pytest\b/,
19
+ /^(?:pnpm\s+)?(?:npx\s+)?vitest\b/,
20
+ /^(?:npx\s+)?jest\b/,
21
+ /^mocha\b/,
22
+ /^ava\b/,
23
+ /^tap\b/,
24
+ ] as const;
25
+
26
+ const TEST_RESULT_PATTERNS = [
27
+ /test result:\s*(\w+)\.\s*(\d+)\s*passed;\s*(\d+)\s*failed;/,
28
+ /(\d+)\s*passed(?:,\s*(\d+)\s*failed)?(?:,\s*(\d+)\s*skipped)?/i,
29
+ /(\d+)\s*pass(?:,\s*(\d+)\s*fail)?(?:,\s*(\d+)\s*skip)?/i,
30
+ /tests?:\s*(\d+)\s*passed(?:,\s*(\d+)\s*failed)?(?:,\s*(\d+)\s*skipped)?/i,
31
+ ];
32
+
33
+ const FAILURE_START_PATTERNS = [
34
+ /^FAIL\s+/,
35
+ /^FAILED\s+/,
36
+ /^\s*●\s+/,
37
+ /^\s*✕\s+/,
38
+ /test\s+\w+\s+\.\.\.\s*FAILED/,
39
+ /thread\s+'\w+'\s+panicked/,
40
+ ];
41
+
42
+ function isFailureStart(line: string): boolean {
43
+ return FAILURE_START_PATTERNS.some((pattern) => pattern.test(line));
44
+ }
45
+
46
+ function extractTestStats(output: string): Partial<TestSummary> {
47
+ for (const pattern of TEST_RESULT_PATTERNS) {
48
+ const match = output.match(pattern);
49
+ if (!match) {
50
+ continue;
51
+ }
52
+ return {
53
+ passed: Number.parseInt(match[1] ?? "0", 10) || 0,
54
+ failed: Number.parseInt(match[2] ?? "0", 10) || 0,
55
+ skipped: Number.parseInt(match[3] ?? "0", 10) || 0,
56
+ };
57
+ }
58
+ return {};
59
+ }
60
+
61
+ export function isTestCommand(command: string | undefined | null): boolean {
62
+ return matchesCommandPatterns(command, TEST_COMMAND_PATTERNS);
63
+ }
64
+
65
+ export function aggregateTestOutput(output: string, command: string | undefined | null): string | null {
66
+ if (!isTestCommand(command)) {
67
+ return null;
68
+ }
69
+
70
+ const lines = output.split("\n");
71
+ const summary: TestSummary = {
72
+ passed: 0,
73
+ failed: 0,
74
+ skipped: 0,
75
+ failures: [],
76
+ };
77
+
78
+ const stats = extractTestStats(output);
79
+ summary.passed = stats.passed ?? 0;
80
+ summary.failed = stats.failed ?? 0;
81
+ summary.skipped = stats.skipped ?? 0;
82
+
83
+ if (summary.passed === 0 && summary.failed === 0) {
84
+ for (const line of lines) {
85
+ if (line.match(/\b(ok|PASS|✓|✔)\b/)) {
86
+ summary.passed++;
87
+ }
88
+ if (line.match(/\b(FAIL|fail|✗|✕)\b/)) {
89
+ summary.failed++;
90
+ }
91
+ }
92
+ }
93
+
94
+ if (summary.failed > 0) {
95
+ let inFailure = false;
96
+ let currentFailure: string[] = [];
97
+ let blankCount = 0;
98
+
99
+ for (const line of lines) {
100
+ if (isFailureStart(line)) {
101
+ if (inFailure && currentFailure.length > 0) {
102
+ summary.failures.push(currentFailure.join("\n"));
103
+ }
104
+ inFailure = true;
105
+ currentFailure = [line];
106
+ blankCount = 0;
107
+ continue;
108
+ }
109
+
110
+ if (!inFailure) {
111
+ continue;
112
+ }
113
+
114
+ if (line.trim() === "") {
115
+ blankCount++;
116
+ if (blankCount >= 2 && currentFailure.length > 3) {
117
+ summary.failures.push(currentFailure.join("\n"));
118
+ inFailure = false;
119
+ currentFailure = [];
120
+ } else {
121
+ currentFailure.push(line);
122
+ }
123
+ continue;
124
+ }
125
+
126
+ if (line.match(/^\s/) || line.match(/^-/)) {
127
+ currentFailure.push(line);
128
+ blankCount = 0;
129
+ continue;
130
+ }
131
+
132
+ summary.failures.push(currentFailure.join("\n"));
133
+ inFailure = false;
134
+ currentFailure = [];
135
+ }
136
+
137
+ if (inFailure && currentFailure.length > 0) {
138
+ summary.failures.push(currentFailure.join("\n"));
139
+ }
140
+ }
141
+
142
+ const result: string[] = ["📋 Test Results:"];
143
+ result.push(` ✅ ${summary.passed} passed`);
144
+ if (summary.failed > 0) {
145
+ result.push(` ❌ ${summary.failed} failed`);
146
+ }
147
+ if (summary.skipped > 0) {
148
+ result.push(` ⏭️ ${summary.skipped} skipped`);
149
+ }
150
+
151
+ if (summary.failed > 0 && summary.failures.length > 0) {
152
+ result.push("\n Failures:");
153
+ for (const failure of summary.failures.slice(0, 5)) {
154
+ const failureLines = failure.split("\n");
155
+ const firstLine = failureLines[0] ?? "";
156
+ result.push(` • ${firstLine.slice(0, 70)}${firstLine.length > 70 ? "..." : ""}`);
157
+ for (const detailLine of failureLines.slice(1, 4)) {
158
+ if (detailLine.trim()) {
159
+ result.push(` ${detailLine.slice(0, 65)}${detailLine.length > 65 ? "..." : ""}`);
160
+ }
161
+ }
162
+ if (failureLines.length > 4) {
163
+ result.push(` ... (${failureLines.length - 4} more lines)`);
164
+ }
165
+ }
166
+ if (summary.failures.length > 5) {
167
+ result.push(` ... and ${summary.failures.length - 5} more failures`);
168
+ }
169
+ }
170
+
171
+ return result.join("\n");
172
+ }
@@ -0,0 +1,11 @@
1
+ export function truncate(text: string, maxLength: number): string {
2
+ if (text.length <= maxLength) {
3
+ return text;
4
+ }
5
+
6
+ if (maxLength < 3) {
7
+ return "...";
8
+ }
9
+
10
+ return `${text.slice(0, maxLength - 3)}...`;
11
+ }
@@ -0,0 +1,131 @@
1
+ declare module "@mariozechner/pi-tui" {
2
+ export interface SettingItem {
3
+ id: string;
4
+ label: string;
5
+ description: string;
6
+ currentValue: string;
7
+ values: string[];
8
+ }
9
+
10
+ export interface AutocompleteItem {
11
+ value: string;
12
+ label: string;
13
+ description?: string;
14
+ }
15
+ }
16
+
17
+ declare module "@mariozechner/pi-coding-agent" {
18
+ interface UiLike {
19
+ notify(message: string, level: "info" | "warning" | "error"): void;
20
+ custom<T>(
21
+ renderer: (
22
+ tui: { requestRender(): void },
23
+ theme: unknown,
24
+ keybindings: unknown,
25
+ done: () => void,
26
+ ) => {
27
+ render(width: number): string[];
28
+ invalidate?(): void;
29
+ handleInput(data: string): void;
30
+ },
31
+ options?: Record<string, unknown>,
32
+ ): Promise<T>;
33
+ }
34
+
35
+ export interface ExtensionContext {
36
+ hasUI: boolean;
37
+ cwd?: string;
38
+ ui: UiLike;
39
+ }
40
+
41
+ export interface ExtensionCommandContext extends ExtensionContext {}
42
+
43
+ export interface ToolResultEvent {
44
+ toolName: string;
45
+ input: Record<string, unknown>;
46
+ content: Array<Record<string, unknown>>;
47
+ details?: unknown;
48
+ }
49
+
50
+ export interface BashToolCallEvent {
51
+ toolName: "bash";
52
+ input: { command: string } & Record<string, unknown>;
53
+ }
54
+
55
+ type MaybePromise<T> = T | Promise<T>;
56
+
57
+ export interface ExtensionAPI {
58
+ exec(
59
+ command: string,
60
+ args: string[],
61
+ options?: { timeout?: number },
62
+ ): Promise<{ code: number; stdout: string; stderr: string }>;
63
+
64
+ on(
65
+ eventName: "tool_call",
66
+ handler: (
67
+ event: Record<string, unknown>,
68
+ ctx: ExtensionContext,
69
+ ) => MaybePromise<Record<string, unknown> | void>,
70
+ ): void;
71
+
72
+ on(
73
+ eventName: "tool_result",
74
+ handler: (
75
+ event: ToolResultEvent,
76
+ ctx: ExtensionContext,
77
+ ) => MaybePromise<Record<string, unknown> | void>,
78
+ ): void;
79
+
80
+ on(
81
+ eventName: "before_agent_start",
82
+ handler: (
83
+ event: { systemPrompt: string },
84
+ ctx: ExtensionContext,
85
+ ) => MaybePromise<{ systemPrompt: string } | Record<string, unknown> | void>,
86
+ ): void;
87
+
88
+ on(
89
+ eventName: string,
90
+ handler: (event: Record<string, unknown>, ctx: ExtensionContext) => MaybePromise<Record<string, unknown> | void>,
91
+ ): void;
92
+
93
+ registerCommand(
94
+ name: string,
95
+ definition: {
96
+ description: string;
97
+ getArgumentCompletions?: (argumentPrefix: string) => Array<{ value: string; label: string; description?: string }> | null;
98
+ handler: (args: string, ctx: ExtensionCommandContext) => MaybePromise<void>;
99
+ },
100
+ ): void;
101
+ }
102
+
103
+ export function isToolCallEventType(
104
+ toolName: "bash",
105
+ event: Record<string, unknown>,
106
+ ): event is BashToolCallEvent;
107
+
108
+ export function isToolCallEventType(
109
+ toolName: string,
110
+ event: Record<string, unknown>,
111
+ ): boolean;
112
+ }
113
+
114
+
115
+ declare module "node:os" {
116
+ export function homedir(): string;
117
+ }
118
+
119
+ declare module "node:path" {
120
+ export function join(...segments: string[]): string;
121
+ export function dirname(path: string): string;
122
+ }
123
+
124
+ declare module "node:fs" {
125
+ export function existsSync(path: string): boolean;
126
+ export function mkdirSync(path: string, options?: { recursive?: boolean }): void;
127
+ export function readFileSync(path: string, encoding: "utf-8"): string;
128
+ export function renameSync(oldPath: string, newPath: string): void;
129
+ export function unlinkSync(path: string): void;
130
+ export function writeFileSync(path: string, data: string, encoding: "utf-8"): void;
131
+ }
package/src/types.ts ADDED
@@ -0,0 +1,114 @@
1
+ export const RTK_MODES = ["rewrite", "suggest"] as const;
2
+ export const RTK_SOURCE_FILTER_LEVELS = ["none", "minimal", "aggressive"] as const;
3
+
4
+ export type RtkMode = (typeof RTK_MODES)[number];
5
+ export type RtkSourceFilterLevel = (typeof RTK_SOURCE_FILTER_LEVELS)[number];
6
+
7
+ export interface RtkOutputCompactionConfig {
8
+ enabled: boolean;
9
+ stripAnsi: boolean;
10
+ truncate: {
11
+ enabled: boolean;
12
+ maxChars: number;
13
+ };
14
+ sourceCodeFilteringEnabled: boolean;
15
+ sourceCodeFiltering: RtkSourceFilterLevel;
16
+ smartTruncate: {
17
+ enabled: boolean;
18
+ maxLines: number;
19
+ };
20
+ aggregateTestOutput: boolean;
21
+ filterBuildOutput: boolean;
22
+ compactGitOutput: boolean;
23
+ aggregateLinterOutput: boolean;
24
+ groupSearchOutput: boolean;
25
+ trackSavings: boolean;
26
+ }
27
+
28
+ export interface RtkIntegrationConfig {
29
+ enabled: boolean;
30
+ mode: RtkMode;
31
+ guardWhenRtkMissing: boolean;
32
+ showRewriteNotifications: boolean;
33
+ rewriteGitGithub: boolean;
34
+ rewriteFilesystem: boolean;
35
+ rewriteRust: boolean;
36
+ rewriteJavaScript: boolean;
37
+ rewritePython: boolean;
38
+ rewriteGo: boolean;
39
+ rewriteContainers: boolean;
40
+ rewriteNetwork: boolean;
41
+ rewritePackageManagers: boolean;
42
+ outputCompaction: RtkOutputCompactionConfig;
43
+ }
44
+
45
+ export const DEFAULT_RTK_INTEGRATION_CONFIG: RtkIntegrationConfig = {
46
+ enabled: true,
47
+ mode: "rewrite",
48
+ guardWhenRtkMissing: true,
49
+ showRewriteNotifications: true,
50
+ rewriteGitGithub: true,
51
+ rewriteFilesystem: true,
52
+ rewriteRust: true,
53
+ rewriteJavaScript: true,
54
+ rewritePython: true,
55
+ rewriteGo: true,
56
+ rewriteContainers: true,
57
+ rewriteNetwork: true,
58
+ rewritePackageManagers: true,
59
+ outputCompaction: {
60
+ enabled: true,
61
+ stripAnsi: true,
62
+ truncate: {
63
+ enabled: true,
64
+ maxChars: 12_000,
65
+ },
66
+ sourceCodeFilteringEnabled: true,
67
+ sourceCodeFiltering: "minimal",
68
+ smartTruncate: {
69
+ enabled: true,
70
+ maxLines: 220,
71
+ },
72
+ aggregateTestOutput: true,
73
+ filterBuildOutput: true,
74
+ compactGitOutput: true,
75
+ aggregateLinterOutput: true,
76
+ groupSearchOutput: true,
77
+ trackSavings: true,
78
+ },
79
+ };
80
+
81
+ export interface ConfigLoadResult {
82
+ config: RtkIntegrationConfig;
83
+ warning?: string;
84
+ }
85
+
86
+ export interface ConfigSaveResult {
87
+ success: boolean;
88
+ error?: string;
89
+ }
90
+
91
+ export interface EnsureConfigResult {
92
+ created: boolean;
93
+ error?: string;
94
+ }
95
+
96
+ export interface RuntimeStatus {
97
+ rtkAvailable: boolean;
98
+ lastCheckedAt?: number;
99
+ lastError?: string;
100
+ }
101
+
102
+ export const RTK_CATEGORY_KEYS = [
103
+ "git",
104
+ "files",
105
+ "rust",
106
+ "js",
107
+ "python",
108
+ "go",
109
+ "containers",
110
+ "network",
111
+ "packages",
112
+ ] as const;
113
+
114
+ export type RtkCategoryKey = (typeof RTK_CATEGORY_KEYS)[number];