pi-rtk-optimizer 0.3.3 β†’ 0.5.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +102 -67
  2. package/README.md +292 -290
  3. package/config/config.example.json +36 -35
  4. package/package.json +4 -4
  5. package/src/additional-coverage-test.ts +278 -0
  6. package/src/boolean-format.ts +3 -0
  7. package/src/command-rewriter-test.ts +160 -120
  8. package/src/command-rewriter.ts +594 -585
  9. package/src/config-modal-test.ts +168 -0
  10. package/src/config-modal.ts +613 -600
  11. package/src/config-store.ts +224 -217
  12. package/src/index-test.ts +54 -0
  13. package/src/index.ts +410 -289
  14. package/src/output-compactor-test.ts +500 -158
  15. package/src/output-compactor.ts +432 -349
  16. package/src/record-utils.ts +6 -0
  17. package/src/rewrite-bypass.ts +332 -173
  18. package/src/rewrite-pipeline-safety.ts +154 -0
  19. package/src/rewrite-rules.ts +255 -255
  20. package/src/rtk-command-environment.ts +64 -0
  21. package/src/runtime-guard-test.ts +42 -50
  22. package/src/runtime-guard.ts +14 -14
  23. package/src/techniques/build.ts +155 -155
  24. package/src/techniques/emoji.ts +91 -0
  25. package/src/techniques/git.ts +231 -229
  26. package/src/techniques/index.ts +10 -16
  27. package/src/techniques/linter.ts +151 -161
  28. package/src/techniques/path-utils.ts +67 -0
  29. package/src/techniques/rtk.ts +136 -0
  30. package/src/techniques/search.ts +67 -76
  31. package/src/techniques/source.ts +253 -253
  32. package/src/techniques/test-output.ts +172 -172
  33. package/src/test-helpers.ts +10 -0
  34. package/src/tool-execution-sanitizer.ts +69 -0
  35. package/src/types-shims.d.ts +192 -183
  36. package/src/types.ts +103 -114
  37. package/src/zellij-modal.ts +1001 -1001
  38. package/src/compat-commands.ts +0 -207
@@ -1,50 +1,42 @@
1
- import assert from "node:assert/strict";
2
-
3
- import {
4
- shouldRequireRtkAvailabilityForCommandHandling,
5
- shouldSkipCommandHandlingWhenRtkMissing,
6
- } from "./runtime-guard.ts";
7
- import { DEFAULT_RTK_INTEGRATION_CONFIG, type RtkIntegrationConfig, type RuntimeStatus } from "./types.ts";
8
-
9
- function runTest(name: string, testFn: () => void): void {
10
- testFn();
11
- console.log(`[PASS] ${name}`);
12
- }
13
-
14
- function cloneConfig(): RtkIntegrationConfig {
15
- return structuredClone(DEFAULT_RTK_INTEGRATION_CONFIG);
16
- }
17
-
18
- function runtimeStatus(rtkAvailable: boolean): RuntimeStatus {
19
- return { rtkAvailable };
20
- }
21
-
22
- runTest("rewrite mode still requires RTK availability when guard is enabled", () => {
23
- const config = cloneConfig();
24
- config.mode = "rewrite";
25
- config.guardWhenRtkMissing = true;
26
-
27
- assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), true);
28
- assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), true);
29
- assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(true)), false);
30
- });
31
-
32
- runTest("suggest mode does not suppress suggestions when RTK is missing", () => {
33
- const config = cloneConfig();
34
- config.mode = "suggest";
35
- config.guardWhenRtkMissing = true;
36
-
37
- assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), false);
38
- assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), false);
39
- });
40
-
41
- runTest("guard disabled never blocks command handling", () => {
42
- const config = cloneConfig();
43
- config.mode = "rewrite";
44
- config.guardWhenRtkMissing = false;
45
-
46
- assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), false);
47
- assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), false);
48
- });
49
-
50
- console.log("All runtime-guard tests passed.");
1
+ import assert from "node:assert/strict";
2
+
3
+ import {
4
+ shouldRequireRtkAvailabilityForCommandHandling,
5
+ shouldSkipCommandHandlingWhenRtkMissing,
6
+ } from "./runtime-guard.ts";
7
+ import { cloneDefaultConfig, runTest } from "./test-helpers.ts";
8
+ import type { RuntimeStatus } from "./types.ts";
9
+
10
+ function runtimeStatus(rtkAvailable: boolean): RuntimeStatus {
11
+ return { rtkAvailable };
12
+ }
13
+
14
+ runTest("rewrite mode still requires RTK availability when guard is enabled", () => {
15
+ const config = cloneDefaultConfig();
16
+ config.mode = "rewrite";
17
+ config.guardWhenRtkMissing = true;
18
+
19
+ assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), true);
20
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), true);
21
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(true)), false);
22
+ });
23
+
24
+ runTest("suggest mode does not suppress suggestions when RTK is missing", () => {
25
+ const config = cloneDefaultConfig();
26
+ config.mode = "suggest";
27
+ config.guardWhenRtkMissing = true;
28
+
29
+ assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), false);
30
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), false);
31
+ });
32
+
33
+ runTest("guard disabled never blocks command handling", () => {
34
+ const config = cloneDefaultConfig();
35
+ config.mode = "rewrite";
36
+ config.guardWhenRtkMissing = false;
37
+
38
+ assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), false);
39
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), false);
40
+ });
41
+
42
+ console.log("All runtime-guard tests passed.");
@@ -1,14 +1,14 @@
1
- import type { RtkIntegrationConfig, RuntimeStatus } from "./types.js";
2
-
3
- export function shouldRequireRtkAvailabilityForCommandHandling(
4
- config: Pick<RtkIntegrationConfig, "mode" | "guardWhenRtkMissing">,
5
- ): boolean {
6
- return config.mode === "rewrite" && config.guardWhenRtkMissing;
7
- }
8
-
9
- export function shouldSkipCommandHandlingWhenRtkMissing(
10
- config: Pick<RtkIntegrationConfig, "mode" | "guardWhenRtkMissing">,
11
- runtimeStatus: Pick<RuntimeStatus, "rtkAvailable">,
12
- ): boolean {
13
- return shouldRequireRtkAvailabilityForCommandHandling(config) && !runtimeStatus.rtkAvailable;
14
- }
1
+ import type { RtkIntegrationConfig, RuntimeStatus } from "./types.js";
2
+
3
+ export function shouldRequireRtkAvailabilityForCommandHandling(
4
+ config: Pick<RtkIntegrationConfig, "mode" | "guardWhenRtkMissing">,
5
+ ): boolean {
6
+ return config.mode === "rewrite" && config.guardWhenRtkMissing;
7
+ }
8
+
9
+ export function shouldSkipCommandHandlingWhenRtkMissing(
10
+ config: Pick<RtkIntegrationConfig, "mode" | "guardWhenRtkMissing">,
11
+ runtimeStatus: Pick<RuntimeStatus, "rtkAvailable">,
12
+ ): boolean {
13
+ return shouldRequireRtkAvailabilityForCommandHandling(config) && !runtimeStatus.rtkAvailable;
14
+ }
@@ -1,155 +1,155 @@
1
- import { matchesCommandPatterns } from "./command-detection.js";
2
-
3
- interface BuildStats {
4
- compiled: number;
5
- errors: string[][];
6
- warnings: string[];
7
- }
8
-
9
- const BUILD_COMMAND_PATTERNS = [
10
- /^cargo\s+(build|check)\b/,
11
- /^bun\s+build\b/,
12
- /^npm\s+run\s+build\b/,
13
- /^yarn\s+build\b/,
14
- /^pnpm\s+build\b/,
15
- /^(?:npx\s+)?tsc\b/,
16
- /^make\b/,
17
- /^cmake\b/,
18
- /^gradle\b/,
19
- /^mvn\b/,
20
- /^go\s+(build|install)\b/,
21
- /^python\s+setup\.py\s+build\b/,
22
- /^pip\s+install\b/,
23
- ] as const;
24
-
25
- const SKIP_PATTERNS = [
26
- /^\s*Compiling\s+/,
27
- /^\s*Checking\s+/,
28
- /^\s*Downloading\s+/,
29
- /^\s*Downloaded\s+/,
30
- /^\s*Fetching\s+/,
31
- /^\s*Fetched\s+/,
32
- /^\s*Updating\s+/,
33
- /^\s*Updated\s+/,
34
- /^\s*Building\s+/,
35
- /^\s*Generated\s+/,
36
- /^\s*Creating\s+/,
37
- /^\s*Running\s+/,
38
- ];
39
-
40
- const ERROR_START_PATTERNS = [/^error\[/, /^error:/, /^\[ERROR\]/, /^FAIL/];
41
- const WARNING_PATTERNS = [/^warning:/, /^\[WARNING\]/, /^warn:/];
42
-
43
- function isSkipLine(line: string): boolean {
44
- return SKIP_PATTERNS.some((pattern) => pattern.test(line));
45
- }
46
-
47
- function isErrorStart(line: string): boolean {
48
- return ERROR_START_PATTERNS.some((pattern) => pattern.test(line));
49
- }
50
-
51
- function isWarning(line: string): boolean {
52
- return WARNING_PATTERNS.some((pattern) => pattern.test(line));
53
- }
54
-
55
- export function isBuildCommand(command: string | undefined | null): boolean {
56
- return matchesCommandPatterns(command, BUILD_COMMAND_PATTERNS);
57
- }
58
-
59
- export function filterBuildOutput(output: string, command: string | undefined | null): string | null {
60
- if (!isBuildCommand(command)) {
61
- return null;
62
- }
63
-
64
- const lines = output.split("\n");
65
- const stats: BuildStats = {
66
- compiled: 0,
67
- errors: [],
68
- warnings: [],
69
- };
70
-
71
- let inErrorBlock = false;
72
- let currentError: string[] = [];
73
- let blankCount = 0;
74
-
75
- for (const line of lines) {
76
- if (line.match(/^\s*(Compiling|Checking|Building)\s+/)) {
77
- stats.compiled++;
78
- continue;
79
- }
80
-
81
- if (isSkipLine(line)) {
82
- continue;
83
- }
84
-
85
- if (isErrorStart(line)) {
86
- if (inErrorBlock && currentError.length > 0) {
87
- stats.errors.push([...currentError]);
88
- }
89
- inErrorBlock = true;
90
- currentError = [line];
91
- blankCount = 0;
92
- continue;
93
- }
94
-
95
- if (isWarning(line)) {
96
- stats.warnings.push(line);
97
- continue;
98
- }
99
-
100
- if (!inErrorBlock) {
101
- continue;
102
- }
103
-
104
- if (line.trim() === "") {
105
- blankCount++;
106
- if (blankCount >= 2 && currentError.length > 3) {
107
- stats.errors.push([...currentError]);
108
- inErrorBlock = false;
109
- currentError = [];
110
- } else {
111
- currentError.push(line);
112
- }
113
- continue;
114
- }
115
-
116
- if (line.match(/^\s/) || line.match(/^-->/)) {
117
- currentError.push(line);
118
- blankCount = 0;
119
- continue;
120
- }
121
-
122
- stats.errors.push([...currentError]);
123
- inErrorBlock = false;
124
- currentError = [];
125
- }
126
-
127
- if (inErrorBlock && currentError.length > 0) {
128
- stats.errors.push(currentError);
129
- }
130
-
131
- if (stats.errors.length === 0 && stats.warnings.length === 0) {
132
- return `βœ“ Build successful (${stats.compiled} units compiled)`;
133
- }
134
-
135
- const result: string[] = [];
136
-
137
- if (stats.errors.length > 0) {
138
- result.push(`❌ ${stats.errors.length} error(s):`);
139
- for (const error of stats.errors.slice(0, 5)) {
140
- result.push(...error.slice(0, 10));
141
- if (error.length > 10) {
142
- result.push(" ...");
143
- }
144
- }
145
- if (stats.errors.length > 5) {
146
- result.push(`... and ${stats.errors.length - 5} more errors`);
147
- }
148
- }
149
-
150
- if (stats.warnings.length > 0) {
151
- result.push(`\n⚠️ ${stats.warnings.length} warning(s)`);
152
- }
153
-
154
- return result.join("\n");
155
- }
1
+ import { matchesCommandPatterns } from "./command-detection.js";
2
+
3
+ interface BuildStats {
4
+ compiled: number;
5
+ errors: string[][];
6
+ warnings: string[];
7
+ }
8
+
9
+ const BUILD_COMMAND_PATTERNS = [
10
+ /^cargo\s+(build|check)\b/,
11
+ /^bun\s+build\b/,
12
+ /^npm\s+run\s+build\b/,
13
+ /^yarn\s+build\b/,
14
+ /^pnpm\s+build\b/,
15
+ /^(?:npx\s+)?tsc\b/,
16
+ /^make\b/,
17
+ /^cmake\b/,
18
+ /^gradle\b/,
19
+ /^mvn\b/,
20
+ /^go\s+(build|install)\b/,
21
+ /^python\s+setup\.py\s+build\b/,
22
+ /^pip\s+install\b/,
23
+ ] as const;
24
+
25
+ const SKIP_PATTERNS = [
26
+ /^\s*Compiling\s+/,
27
+ /^\s*Checking\s+/,
28
+ /^\s*Downloading\s+/,
29
+ /^\s*Downloaded\s+/,
30
+ /^\s*Fetching\s+/,
31
+ /^\s*Fetched\s+/,
32
+ /^\s*Updating\s+/,
33
+ /^\s*Updated\s+/,
34
+ /^\s*Building\s+/,
35
+ /^\s*Generated\s+/,
36
+ /^\s*Creating\s+/,
37
+ /^\s*Running\s+/,
38
+ ];
39
+
40
+ const ERROR_START_PATTERNS = [/^error\[/, /^error:/, /^\[ERROR\]/, /^FAIL/];
41
+ const WARNING_PATTERNS = [/^warning:/, /^\[WARNING\]/, /^warn:/];
42
+
43
+ function isSkipLine(line: string): boolean {
44
+ return SKIP_PATTERNS.some((pattern) => pattern.test(line));
45
+ }
46
+
47
+ function isErrorStart(line: string): boolean {
48
+ return ERROR_START_PATTERNS.some((pattern) => pattern.test(line));
49
+ }
50
+
51
+ function isWarning(line: string): boolean {
52
+ return WARNING_PATTERNS.some((pattern) => pattern.test(line));
53
+ }
54
+
55
+ export function isBuildCommand(command: string | undefined | null): boolean {
56
+ return matchesCommandPatterns(command, BUILD_COMMAND_PATTERNS);
57
+ }
58
+
59
+ export function filterBuildOutput(output: string, command: string | undefined | null): string | null {
60
+ if (!isBuildCommand(command)) {
61
+ return null;
62
+ }
63
+
64
+ const lines = output.split("\n");
65
+ const stats: BuildStats = {
66
+ compiled: 0,
67
+ errors: [],
68
+ warnings: [],
69
+ };
70
+
71
+ let inErrorBlock = false;
72
+ let currentError: string[] = [];
73
+ let blankCount = 0;
74
+
75
+ for (const line of lines) {
76
+ if (line.match(/^\s*(Compiling|Checking|Building)\s+/)) {
77
+ stats.compiled++;
78
+ continue;
79
+ }
80
+
81
+ if (isSkipLine(line)) {
82
+ continue;
83
+ }
84
+
85
+ if (isErrorStart(line)) {
86
+ if (inErrorBlock && currentError.length > 0) {
87
+ stats.errors.push([...currentError]);
88
+ }
89
+ inErrorBlock = true;
90
+ currentError = [line];
91
+ blankCount = 0;
92
+ continue;
93
+ }
94
+
95
+ if (isWarning(line)) {
96
+ stats.warnings.push(line);
97
+ continue;
98
+ }
99
+
100
+ if (!inErrorBlock) {
101
+ continue;
102
+ }
103
+
104
+ if (line.trim() === "") {
105
+ blankCount++;
106
+ if (blankCount >= 2 && currentError.length > 3) {
107
+ stats.errors.push([...currentError]);
108
+ inErrorBlock = false;
109
+ currentError = [];
110
+ } else {
111
+ currentError.push(line);
112
+ }
113
+ continue;
114
+ }
115
+
116
+ if (line.match(/^\s/) || line.match(/^-->/)) {
117
+ currentError.push(line);
118
+ blankCount = 0;
119
+ continue;
120
+ }
121
+
122
+ stats.errors.push([...currentError]);
123
+ inErrorBlock = false;
124
+ currentError = [];
125
+ }
126
+
127
+ if (inErrorBlock && currentError.length > 0) {
128
+ stats.errors.push(currentError);
129
+ }
130
+
131
+ if (stats.errors.length === 0 && stats.warnings.length === 0) {
132
+ return `[OK] Build successful (${stats.compiled} units compiled)`;
133
+ }
134
+
135
+ const result: string[] = [];
136
+
137
+ if (stats.errors.length > 0) {
138
+ result.push(`[ERROR] ${stats.errors.length} error(s):`);
139
+ for (const error of stats.errors.slice(0, 5)) {
140
+ result.push(...error.slice(0, 10));
141
+ if (error.length > 10) {
142
+ result.push(" ...");
143
+ }
144
+ }
145
+ if (stats.errors.length > 5) {
146
+ result.push(`... and ${stats.errors.length - 5} more errors`);
147
+ }
148
+ }
149
+
150
+ if (stats.warnings.length > 0) {
151
+ result.push(`\n[WARN] ${stats.warnings.length} warning(s)`);
152
+ }
153
+
154
+ return result.join("\n");
155
+ }
@@ -0,0 +1,91 @@
1
+ const RTK_COMMAND_PATTERN = /^\s*rtk(?:\.exe)?(?:\s|$)/;
2
+ const RTK_OUTPUT_SIGNATURE_PATTERNS = [
3
+ /^πŸ“‚ PATH Variables:/m,
4
+ /^πŸ”§ Language\/Runtime:/m,
5
+ /^☁️?\s+Cloud\/Services:/m,
6
+ /^πŸ› οΈ?\s+Tools:/m,
7
+ /^πŸ“‹ Other:/m,
8
+ /^πŸ“Š Total:/m,
9
+ /^πŸ“Š\s+.+\s+β†’\s+.+/m,
10
+ /^πŸ“Œ\s+/m,
11
+ /^βœ… Files are identical$/m,
12
+ /^βœ… Staged:/m,
13
+ /^πŸ“ Modified:/m,
14
+ /^❓ Untracked:/m,
15
+ /^⚠️?\s+Conflicts:/m,
16
+ /^πŸ” CI Checks Summary:/m,
17
+ /^πŸ”\s+\d+\s+in\s+\d+F:/m,
18
+ /^--- Changes ---$/m,
19
+ /^πŸ“„\s+.+$/m,
20
+ /^πŸ“\s+\d+F\s+\d+D:/m,
21
+ /^☸️?\s+\d+\s+pods:/m,
22
+ /^πŸ“¦\s+/m,
23
+ ] as const;
24
+
25
+ const LINE_PREFIX_REPLACEMENTS: Array<{ pattern: RegExp; replacement: string }> = [
26
+ { pattern: /^πŸ”\s+/gm, replacement: "" },
27
+ { pattern: /^πŸ“„\s+/gm, replacement: "> " },
28
+ { pattern: /^πŸ“‚\s+/gm, replacement: "" },
29
+ { pattern: /^πŸ”§\s+/gm, replacement: "" },
30
+ { pattern: /^☁️?\s+/gm, replacement: "" },
31
+ { pattern: /^πŸ› οΈ?\s+/gm, replacement: "" },
32
+ { pattern: /^πŸ“‹\s+/gm, replacement: "" },
33
+ { pattern: /^πŸ“Š\s+/gm, replacement: "" },
34
+ { pattern: /^πŸ“Œ\s+/gm, replacement: "Branch: " },
35
+ { pattern: /^πŸ“\s+/gm, replacement: "" },
36
+ { pattern: /^πŸ“¦\s+/gm, replacement: "" },
37
+ { pattern: /^πŸ“\s+/gm, replacement: "" },
38
+ { pattern: /^☸️?\s+/gm, replacement: "" },
39
+ ] as const;
40
+
41
+ const INLINE_REPLACEMENTS: Array<{ pattern: RegExp; replacement: string }> = [
42
+ { pattern: /βœ…|βœ“|βœ”/g, replacement: "[OK]" },
43
+ { pattern: /❌|βœ—|βœ•/g, replacement: "[ERROR]" },
44
+ { pattern: /⚠️|⚠/g, replacement: "[WARN]" },
45
+ { pattern: /❓/g, replacement: "[INFO]" },
46
+ { pattern: /⏭️|⏭/g, replacement: "[SKIP]" },
47
+ { pattern: /⏳/g, replacement: "Pending" },
48
+ { pattern: /⬆️|⬆/g, replacement: "up" },
49
+ { pattern: /β†’/g, replacement: "->" },
50
+ { pattern: /β€’/g, replacement: "-" },
51
+ ] as const;
52
+
53
+ const REMAINING_EMOJI_PATTERN = /\p{Extended_Pictographic}/gu;
54
+ const EMOJI_VARIATION_SELECTOR_PATTERN = /\uFE0F/g;
55
+ const INLINE_LABEL_SPACING_PATTERN = /(\[[A-Z]+\])(\S)/g;
56
+
57
+ function isRtkCommand(command: string | undefined | null): boolean {
58
+ return typeof command === "string" && RTK_COMMAND_PATTERN.test(command);
59
+ }
60
+
61
+ function looksLikeRtkStyledOutput(output: string): boolean {
62
+ return RTK_OUTPUT_SIGNATURE_PATTERNS.some((pattern) => pattern.test(output));
63
+ }
64
+
65
+ /**
66
+ * RTK emits emoji-heavy presentation in several command outputs. Pi should
67
+ * present tool results as plain text, so normalize RTK output markers before
68
+ * the agent consumes them. We apply this to explicit `rtk ...` commands and to
69
+ * recognizable RTK-shaped output that may have been prefixed by another layer.
70
+ */
71
+ export function sanitizeRtkEmojiOutput(output: string, command: string | undefined | null): string | null {
72
+ if (!isRtkCommand(command) && !looksLikeRtkStyledOutput(output)) {
73
+ return null;
74
+ }
75
+
76
+ let nextText = output;
77
+
78
+ for (const { pattern, replacement } of LINE_PREFIX_REPLACEMENTS) {
79
+ nextText = nextText.replace(pattern, replacement);
80
+ }
81
+
82
+ for (const { pattern, replacement } of INLINE_REPLACEMENTS) {
83
+ nextText = nextText.replace(pattern, replacement);
84
+ }
85
+
86
+ nextText = nextText.replace(REMAINING_EMOJI_PATTERN, "");
87
+ nextText = nextText.replace(EMOJI_VARIATION_SELECTOR_PATTERN, "");
88
+ nextText = nextText.replace(INLINE_LABEL_SPACING_PATTERN, "$1 $2");
89
+
90
+ return nextText === output ? null : nextText;
91
+ }