pi-rtk 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/README.md +116 -0
- package/RTK.md +845 -0
- package/config.ts +84 -0
- package/index.ts +228 -0
- package/metrics.ts +83 -0
- package/package.json +35 -0
- package/techniques/ansi.ts +19 -0
- package/techniques/build.ts +172 -0
- package/techniques/git.ts +245 -0
- package/techniques/index.ts +16 -0
- package/techniques/linter.ts +191 -0
- package/techniques/search.ts +100 -0
- package/techniques/source.ts +246 -0
- package/techniques/test-output.ts +172 -0
- package/techniques/truncate.ts +27 -0
|
@@ -0,0 +1,246 @@
|
|
|
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
|
+
docComment?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const COMMENT_PATTERNS: Record<Language, CommentPatterns> = {
|
|
38
|
+
typescript: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "/**" },
|
|
39
|
+
javascript: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "/**" },
|
|
40
|
+
python: { line: "#", docComment: '"""' },
|
|
41
|
+
rust: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "///" },
|
|
42
|
+
go: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "//" },
|
|
43
|
+
java: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "/**" },
|
|
44
|
+
c: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "/*" },
|
|
45
|
+
cpp: { line: "//", blockStart: "/*", blockEnd: "*/", docComment: "/*" },
|
|
46
|
+
unknown: {},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function detectLanguage(filePath: string): Language {
|
|
50
|
+
const lastDot = filePath.lastIndexOf(".");
|
|
51
|
+
if (lastDot === -1) return "unknown";
|
|
52
|
+
const ext = filePath.slice(lastDot).toLowerCase();
|
|
53
|
+
return LANGUAGE_EXTENSIONS[ext] || "unknown";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function filterMinimal(content: string, language: Language): string {
|
|
57
|
+
if (language === "unknown") {
|
|
58
|
+
return content;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const patterns = COMMENT_PATTERNS[language];
|
|
62
|
+
const lines = content.split("\n");
|
|
63
|
+
const result: string[] = [];
|
|
64
|
+
let inBlockComment = false;
|
|
65
|
+
let inDocstring = false;
|
|
66
|
+
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
|
|
70
|
+
// Handle Python docstrings
|
|
71
|
+
if (language === "python" && patterns.docComment) {
|
|
72
|
+
if (trimmed.startsWith(patterns.docComment)) {
|
|
73
|
+
inDocstring = !inDocstring;
|
|
74
|
+
result.push(line);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (inDocstring) {
|
|
78
|
+
result.push(line);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle block comments
|
|
84
|
+
if (patterns.blockStart && trimmed.startsWith(patterns.blockStart)) {
|
|
85
|
+
// Check if it's a doc comment (keep it)
|
|
86
|
+
if (patterns.docComment && trimmed.startsWith(patterns.docComment)) {
|
|
87
|
+
result.push(line);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
inBlockComment = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (inBlockComment) {
|
|
94
|
+
if (patterns.blockEnd && trimmed.endsWith(patterns.blockEnd)) {
|
|
95
|
+
inBlockComment = false;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle line comments
|
|
101
|
+
if (patterns.line) {
|
|
102
|
+
const commentIndex = line.indexOf(patterns.line);
|
|
103
|
+
if (commentIndex >= 0) {
|
|
104
|
+
// Check if it's a doc comment
|
|
105
|
+
if (patterns.docComment && trimmed.startsWith(patterns.docComment)) {
|
|
106
|
+
result.push(line);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Remove the comment portion
|
|
110
|
+
result.push(line.slice(0, commentIndex));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
result.push(line);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Normalize multiple blank lines
|
|
119
|
+
const normalized = result
|
|
120
|
+
.join("\n")
|
|
121
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
122
|
+
.trim();
|
|
123
|
+
|
|
124
|
+
return normalized;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function filterAggressive(content: string, language: Language): string {
|
|
128
|
+
if (language === "unknown") {
|
|
129
|
+
return filterMinimal(content, language);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const minimal = filterMinimal(content, language);
|
|
133
|
+
const lines = minimal.split("\n");
|
|
134
|
+
const result: string[] = [];
|
|
135
|
+
let braceDepth = 0;
|
|
136
|
+
let inImplementation = false;
|
|
137
|
+
|
|
138
|
+
// Patterns to preserve
|
|
139
|
+
const importPattern = /^(use\s+|import\s+|from\s+|require\(|#include)/;
|
|
140
|
+
const signaturePattern =
|
|
141
|
+
/^(pub\s+)?(async\s+)?(fn|def|function|func|class|struct|enum|trait|interface|type)\s+\w+/;
|
|
142
|
+
const constPattern = /^(const|static|let|pub\s+const|pub\s+static)\s+/;
|
|
143
|
+
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
const trimmed = line.trim();
|
|
146
|
+
|
|
147
|
+
// Always keep imports
|
|
148
|
+
if (importPattern.test(trimmed)) {
|
|
149
|
+
result.push(line);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Keep function/type signatures
|
|
154
|
+
if (signaturePattern.test(trimmed)) {
|
|
155
|
+
result.push(line);
|
|
156
|
+
inImplementation = true;
|
|
157
|
+
braceDepth = 0;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Track brace depth for bodies
|
|
162
|
+
if (inImplementation) {
|
|
163
|
+
const openBraces = (trimmed.match(/\{/g) || []).length;
|
|
164
|
+
const closeBraces = (trimmed.match(/\}/g) || []).length;
|
|
165
|
+
braceDepth += openBraces - closeBraces;
|
|
166
|
+
|
|
167
|
+
// Only keep opening/closing braces
|
|
168
|
+
if (braceDepth <= 1 && (trimmed === "{" || trimmed === "}" || trimmed.endsWith("{"))) {
|
|
169
|
+
result.push(line);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (braceDepth <= 0) {
|
|
173
|
+
inImplementation = false;
|
|
174
|
+
if (trimmed !== "" && trimmed !== "}") {
|
|
175
|
+
result.push(" // ... implementation");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Keep constants and type definitions
|
|
182
|
+
if (constPattern.test(trimmed)) {
|
|
183
|
+
result.push(line);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result.join("\n").trim();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function smartTruncate(content: string, maxLines: number, language: Language): string {
|
|
191
|
+
const lines = content.split("\n");
|
|
192
|
+
if (lines.length <= maxLines) {
|
|
193
|
+
return content;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result: string[] = [];
|
|
197
|
+
let keptLines = 0;
|
|
198
|
+
let skippedSection = false;
|
|
199
|
+
|
|
200
|
+
// Patterns for important lines
|
|
201
|
+
const importantPattern =
|
|
202
|
+
/^(import|use|from|require|#include|fn|def|function|func|class|struct|enum|trait|interface|type|const|static|let|pub\s)/;
|
|
203
|
+
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
const trimmed = line.trim();
|
|
206
|
+
const isImportant = importantPattern.test(trimmed);
|
|
207
|
+
|
|
208
|
+
if (isImportant || keptLines < maxLines / 2) {
|
|
209
|
+
if (skippedSection) {
|
|
210
|
+
result.push(` // ... ${lines.length - result.length - keptLines} lines omitted`);
|
|
211
|
+
skippedSection = false;
|
|
212
|
+
}
|
|
213
|
+
result.push(line);
|
|
214
|
+
keptLines++;
|
|
215
|
+
} else {
|
|
216
|
+
skippedSection = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (keptLines >= maxLines - 1) {
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (skippedSection || keptLines < lines.length) {
|
|
225
|
+
result.push(`// ... ${lines.length - keptLines} more lines (total: ${lines.length})`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result.join("\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function filterSourceCode(
|
|
232
|
+
content: string,
|
|
233
|
+
language: Language,
|
|
234
|
+
level: "none" | "minimal" | "aggressive"
|
|
235
|
+
): string {
|
|
236
|
+
switch (level) {
|
|
237
|
+
case "none":
|
|
238
|
+
return content;
|
|
239
|
+
case "minimal":
|
|
240
|
+
return filterMinimal(content, language);
|
|
241
|
+
case "aggressive":
|
|
242
|
+
return filterAggressive(content, language);
|
|
243
|
+
default:
|
|
244
|
+
return content;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
interface TestSummary {
|
|
2
|
+
passed: number;
|
|
3
|
+
failed: number;
|
|
4
|
+
skipped: number;
|
|
5
|
+
failures: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const TEST_COMMANDS = [
|
|
9
|
+
"test",
|
|
10
|
+
"jest",
|
|
11
|
+
"vitest",
|
|
12
|
+
"pytest",
|
|
13
|
+
"cargo test",
|
|
14
|
+
"bun test",
|
|
15
|
+
"go test",
|
|
16
|
+
"mocha",
|
|
17
|
+
"ava",
|
|
18
|
+
"tap",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const TEST_RESULT_PATTERNS = [
|
|
22
|
+
/test result:\s*(\w+)\.\s*(\d+)\s*passed;\s*(\d+)\s*failed;/,
|
|
23
|
+
/(\d+)\s*passed(?:,\s*(\d+)\s*failed)?(?:,\s*(\d+)\s*skipped)?/i,
|
|
24
|
+
/(\d+)\s*pass(?:,\s*(\d+)\s*fail)?(?:,\s*(\d+)\s*skip)?/i,
|
|
25
|
+
/tests?:\s*(\d+)\s*passed(?:,\s*(\d+)\s*failed)?(?:,\s*(\d+)\s*skipped)?/i,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const FAILURE_START_PATTERNS = [
|
|
29
|
+
/^FAIL\s+/,
|
|
30
|
+
/^FAILED\s+/,
|
|
31
|
+
/^\s*●\s+/,
|
|
32
|
+
/^\s*✕\s+/,
|
|
33
|
+
/test\s+\w+\s+\.\.\.\s*FAILED/,
|
|
34
|
+
/thread\s+'\w+'\s+panicked/,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function isTestCommand(command: string | undefined | null): boolean {
|
|
38
|
+
if (typeof command !== "string" || command.length === 0) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cmdLower = command.toLowerCase();
|
|
43
|
+
return TEST_COMMANDS.some((tc) => cmdLower.includes(tc.toLowerCase()));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isFailureStart(line: string): boolean {
|
|
47
|
+
return FAILURE_START_PATTERNS.some((pattern) => pattern.test(line));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractTestStats(output: string): Partial<TestSummary> {
|
|
51
|
+
const summary: Partial<TestSummary> = {};
|
|
52
|
+
|
|
53
|
+
for (const pattern of TEST_RESULT_PATTERNS) {
|
|
54
|
+
const match = output.match(pattern);
|
|
55
|
+
if (match) {
|
|
56
|
+
summary.passed = parseInt(match[1], 10) || 0;
|
|
57
|
+
summary.failed = parseInt(match[2], 10) || 0;
|
|
58
|
+
summary.skipped = parseInt(match[3], 10) || 0;
|
|
59
|
+
return summary;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return summary;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function aggregateTestOutput(
|
|
67
|
+
output: string,
|
|
68
|
+
command: string | undefined | null
|
|
69
|
+
): string | null {
|
|
70
|
+
if (typeof command !== "string" || !isTestCommand(command)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const lines = output.split("\n");
|
|
75
|
+
const summary: TestSummary = {
|
|
76
|
+
passed: 0,
|
|
77
|
+
failed: 0,
|
|
78
|
+
skipped: 0,
|
|
79
|
+
failures: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Extract stats from output
|
|
83
|
+
const stats = extractTestStats(output);
|
|
84
|
+
summary.passed = stats.passed || 0;
|
|
85
|
+
summary.failed = stats.failed || 0;
|
|
86
|
+
summary.skipped = stats.skipped || 0;
|
|
87
|
+
|
|
88
|
+
// Fallback: count passes/fails manually if no stats found
|
|
89
|
+
if (summary.passed === 0 && summary.failed === 0) {
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
if (line.match(/\b(ok|PASS|✓|✔)\b/)) summary.passed++;
|
|
92
|
+
if (line.match(/\b(FAIL|fail|✗|✕)\b/)) summary.failed++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Extract failure details if tests failed
|
|
97
|
+
if (summary.failed > 0) {
|
|
98
|
+
let inFailure = false;
|
|
99
|
+
let currentFailure: string[] = [];
|
|
100
|
+
let blankCount = 0;
|
|
101
|
+
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
if (isFailureStart(line)) {
|
|
104
|
+
if (inFailure && currentFailure.length > 0) {
|
|
105
|
+
summary.failures.push(currentFailure.join("\n"));
|
|
106
|
+
}
|
|
107
|
+
inFailure = true;
|
|
108
|
+
currentFailure = [line];
|
|
109
|
+
blankCount = 0;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (inFailure) {
|
|
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
|
+
} else if (line.match(/^\s/) || line.match(/^-/)) {
|
|
124
|
+
// Continuation of failure
|
|
125
|
+
currentFailure.push(line);
|
|
126
|
+
blankCount = 0;
|
|
127
|
+
} else {
|
|
128
|
+
// End of failure block
|
|
129
|
+
summary.failures.push(currentFailure.join("\n"));
|
|
130
|
+
inFailure = false;
|
|
131
|
+
currentFailure = [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (inFailure && currentFailure.length > 0) {
|
|
137
|
+
summary.failures.push(currentFailure.join("\n"));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Format output
|
|
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 lines = failure.split("\n");
|
|
155
|
+
const firstLine = lines[0];
|
|
156
|
+
result.push(` • ${firstLine.slice(0, 70)}${firstLine.length > 70 ? "..." : ""}`);
|
|
157
|
+
for (const line of lines.slice(1, 4)) {
|
|
158
|
+
if (line.trim()) {
|
|
159
|
+
result.push(` ${line.slice(0, 65)}${line.length > 65 ? "..." : ""}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (lines.length > 4) {
|
|
163
|
+
result.push(` ... (${lines.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,27 @@
|
|
|
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
|
+
}
|
|
12
|
+
|
|
13
|
+
export function truncateLines(text: string, maxLines: number): string {
|
|
14
|
+
const lines = text.split("\n");
|
|
15
|
+
if (lines.length <= maxLines) {
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const keepLines = Math.floor(maxLines / 2);
|
|
20
|
+
const result = [
|
|
21
|
+
...lines.slice(0, keepLines),
|
|
22
|
+
`\n... ${lines.length - maxLines} lines omitted ...\n`,
|
|
23
|
+
...lines.slice(-keepLines),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
return result.join("\n");
|
|
27
|
+
}
|