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,172 +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
- }
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(` PASS: ${summary.passed} passed`);
144
+ if (summary.failed > 0) {
145
+ result.push(` FAIL: ${summary.failed} failed`);
146
+ }
147
+ if (summary.skipped > 0) {
148
+ result.push(` SKIP: ${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,10 @@
1
+ import { DEFAULT_RTK_INTEGRATION_CONFIG, type RtkIntegrationConfig } from "./types.ts";
2
+
3
+ export function runTest(name: string, testFn: () => void): void {
4
+ testFn();
5
+ console.log(`[PASS] ${name}`);
6
+ }
7
+
8
+ export function cloneDefaultConfig(): RtkIntegrationConfig {
9
+ return structuredClone(DEFAULT_RTK_INTEGRATION_CONFIG);
10
+ }
@@ -0,0 +1,69 @@
1
+ import { toRecord } from "./record-utils.js";
2
+ import { sanitizeRtkEmojiOutput, stripAnsiFast, stripRtkHookWarnings } from "./techniques/index.js";
3
+
4
+ interface ToolResultTextBlock {
5
+ type: string;
6
+ text?: string;
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ function sanitizeStreamingBashText(text: string, command: string | undefined | null): string {
11
+ let nextText = stripAnsiFast(text);
12
+
13
+ const withoutRtkHookWarnings = stripRtkHookWarnings(nextText, command);
14
+ if (withoutRtkHookWarnings !== null) {
15
+ nextText = withoutRtkHookWarnings;
16
+ }
17
+
18
+ const withoutRtkEmoji = sanitizeRtkEmojiOutput(nextText, command);
19
+ if (withoutRtkEmoji !== null) {
20
+ nextText = withoutRtkEmoji;
21
+ }
22
+
23
+ return nextText;
24
+ }
25
+
26
+ /**
27
+ * Sanitizes streamed bash result blocks before the TUI renders them so RTK
28
+ * self-diagnostics never flash in partial or final tool output.
29
+ */
30
+ export function sanitizeStreamingBashExecutionResult(
31
+ result: unknown,
32
+ command: string | undefined | null,
33
+ ): boolean {
34
+ const resultRecord = toRecord(result);
35
+ const sourceContent = Array.isArray(resultRecord.content) ? resultRecord.content : null;
36
+ if (!sourceContent || sourceContent.length === 0) {
37
+ return false;
38
+ }
39
+
40
+ let changed = false;
41
+ const nextContent = sourceContent.map((block) => {
42
+ if (!block || typeof block !== "object" || Array.isArray(block)) {
43
+ return block;
44
+ }
45
+
46
+ const contentBlock = block as ToolResultTextBlock;
47
+ if (contentBlock.type !== "text" || typeof contentBlock.text !== "string") {
48
+ return block;
49
+ }
50
+
51
+ const sanitizedText = sanitizeStreamingBashText(contentBlock.text, command);
52
+ if (sanitizedText === contentBlock.text) {
53
+ return block;
54
+ }
55
+
56
+ changed = true;
57
+ return {
58
+ ...contentBlock,
59
+ text: sanitizedText,
60
+ };
61
+ });
62
+
63
+ if (!changed) {
64
+ return false;
65
+ }
66
+
67
+ resultRecord.content = nextContent;
68
+ return true;
69
+ }