@wingsbutterfly/dsh-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.
Files changed (94) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/README.zh.md +211 -0
  5. package/THIRD_PARTY_NOTICES.md +51 -0
  6. package/docs/assets/how-it-works.svg +47 -0
  7. package/docs/verification.md +279 -0
  8. package/lib/command.d.ts +41 -0
  9. package/lib/command.d.ts.map +1 -0
  10. package/lib/command.js +137 -0
  11. package/lib/command.js.map +1 -0
  12. package/lib/compact/build.d.ts +15 -0
  13. package/lib/compact/build.d.ts.map +1 -0
  14. package/lib/compact/build.js +140 -0
  15. package/lib/compact/build.js.map +1 -0
  16. package/lib/compact/detect.d.ts +22 -0
  17. package/lib/compact/detect.d.ts.map +1 -0
  18. package/lib/compact/detect.js +54 -0
  19. package/lib/compact/detect.js.map +1 -0
  20. package/lib/compact/dsh-result.d.ts +49 -0
  21. package/lib/compact/dsh-result.d.ts.map +1 -0
  22. package/lib/compact/dsh-result.js +84 -0
  23. package/lib/compact/dsh-result.js.map +1 -0
  24. package/lib/compact/git.d.ts +21 -0
  25. package/lib/compact/git.d.ts.map +1 -0
  26. package/lib/compact/git.js +197 -0
  27. package/lib/compact/git.js.map +1 -0
  28. package/lib/compact/index.d.ts +39 -0
  29. package/lib/compact/index.d.ts.map +1 -0
  30. package/lib/compact/index.js +236 -0
  31. package/lib/compact/index.js.map +1 -0
  32. package/lib/compact/linter.d.ts +12 -0
  33. package/lib/compact/linter.d.ts.map +1 -0
  34. package/lib/compact/linter.js +118 -0
  35. package/lib/compact/linter.js.map +1 -0
  36. package/lib/compact/search.d.ts +16 -0
  37. package/lib/compact/search.d.ts.map +1 -0
  38. package/lib/compact/search.js +67 -0
  39. package/lib/compact/search.js.map +1 -0
  40. package/lib/compact/source.d.ts +22 -0
  41. package/lib/compact/source.d.ts.map +1 -0
  42. package/lib/compact/source.js +224 -0
  43. package/lib/compact/source.js.map +1 -0
  44. package/lib/compact/test-output.d.ts +12 -0
  45. package/lib/compact/test-output.d.ts.map +1 -0
  46. package/lib/compact/test-output.js +168 -0
  47. package/lib/compact/test-output.js.map +1 -0
  48. package/lib/compact/text.d.ts +22 -0
  49. package/lib/compact/text.d.ts.map +1 -0
  50. package/lib/compact/text.js +87 -0
  51. package/lib/compact/text.js.map +1 -0
  52. package/lib/config.d.ts +243 -0
  53. package/lib/config.d.ts.map +1 -0
  54. package/lib/config.js +183 -0
  55. package/lib/config.js.map +1 -0
  56. package/lib/index.d.ts +46 -0
  57. package/lib/index.d.ts.map +1 -0
  58. package/lib/index.js +405 -0
  59. package/lib/index.js.map +1 -0
  60. package/lib/metrics.d.ts +35 -0
  61. package/lib/metrics.d.ts.map +1 -0
  62. package/lib/metrics.js +51 -0
  63. package/lib/metrics.js.map +1 -0
  64. package/lib/rtk-executable.d.ts +51 -0
  65. package/lib/rtk-executable.d.ts.map +1 -0
  66. package/lib/rtk-executable.js +75 -0
  67. package/lib/rtk-executable.js.map +1 -0
  68. package/lib/rtk-rewrite.d.ts +88 -0
  69. package/lib/rtk-rewrite.d.ts.map +1 -0
  70. package/lib/rtk-rewrite.js +150 -0
  71. package/lib/rtk-rewrite.js.map +1 -0
  72. package/lib/runtime-guard.d.ts +31 -0
  73. package/lib/runtime-guard.d.ts.map +1 -0
  74. package/lib/runtime-guard.js +32 -0
  75. package/lib/runtime-guard.js.map +1 -0
  76. package/package.json +82 -0
  77. package/scripts/link-dsh.mjs +135 -0
  78. package/src/command.ts +174 -0
  79. package/src/compact/build.ts +154 -0
  80. package/src/compact/detect.ts +54 -0
  81. package/src/compact/dsh-result.ts +99 -0
  82. package/src/compact/git.ts +209 -0
  83. package/src/compact/index.ts +284 -0
  84. package/src/compact/linter.ts +126 -0
  85. package/src/compact/search.ts +73 -0
  86. package/src/compact/source.ts +244 -0
  87. package/src/compact/test-output.ts +184 -0
  88. package/src/compact/text.ts +86 -0
  89. package/src/config.ts +263 -0
  90. package/src/index.ts +431 -0
  91. package/src/metrics.ts +84 -0
  92. package/src/rtk-executable.ts +117 -0
  93. package/src/rtk-rewrite.ts +179 -0
  94. package/src/runtime-guard.ts +44 -0
@@ -0,0 +1,236 @@
1
+ import { homedir } from 'node:os';
2
+ import { resolve } from 'node:path';
3
+ import { aggregateLinterOutput } from './linter.js';
4
+ import { filterBuildOutput } from './build.js';
5
+ import { aggregateTestOutput } from './test-output.js';
6
+ import { compactGitOutput } from './git.js';
7
+ import { groupSearchResults } from './search.js';
8
+ import { detectLanguage, filterSourceCode, smartTruncate } from './source.js';
9
+ import { NO_OUTPUT_PLACEHOLDER, parseBashResult, renderBashResult } from './dsh-result.js';
10
+ import { countLines, stripAnsi, truncate } from './text.js';
11
+ /** Techniques whose effect cannot be undone by re-reading a smaller range. */
12
+ const LOSSY_TECHNIQUES = ['build', 'test', 'git', 'linter', 'search', 'truncate', 'smart-truncate', 'source:'];
13
+ /** Reads at or below this many lines are always left exact. */
14
+ const READ_EXACT_LINE_THRESHOLD = 80;
15
+ /** Banner prefixed to a `read` result that lost information. */
16
+ export const READ_COMPACTION_BANNER_PREFIX = '[rtk compacted output:';
17
+ function asRecord(value) {
18
+ return typeof value === 'object' && value !== null ? value : {};
19
+ }
20
+ function asArray(value) {
21
+ return Array.isArray(value) ? value : [];
22
+ }
23
+ function isTextBlock(block) {
24
+ const record = asRecord(block);
25
+ return record.type === 'text' && typeof record.text === 'string';
26
+ }
27
+ function hasLossyTechnique(techniques) {
28
+ return techniques.some((technique) => LOSSY_TECHNIQUES.some((prefix) => (prefix.endsWith(':') ? technique.startsWith(prefix) : technique === prefix)));
29
+ }
30
+ /** Apply one nullable technique, recording it only when it changed the text. */
31
+ function applyNullable(state, result, technique) {
32
+ if (result === null || result === state.text)
33
+ return;
34
+ state.text = result;
35
+ state.techniques.push(technique);
36
+ }
37
+ function applyAnsi(state, compaction) {
38
+ if (!compaction.stripAnsi)
39
+ return;
40
+ const stripped = stripAnsi(state.text);
41
+ if (stripped !== state.text) {
42
+ state.text = stripped;
43
+ state.techniques.push('ansi');
44
+ }
45
+ }
46
+ /**
47
+ * Compact a bash result without disturbing its status markers.
48
+ *
49
+ * stdout is summarized by whichever technique recognizes the command; stderr is
50
+ * only stripped of escape codes, because an error message's exact wording is
51
+ * what the reader acts on. The trailing markers are re-attached verbatim, and
52
+ * a final budget check truncates the bodies while never cutting a marker.
53
+ */
54
+ function compactBashText(text, command, compaction) {
55
+ const parts = parseBashResult(text);
56
+ if (parts.empty)
57
+ return { text, techniques: [] };
58
+ const state = { text: parts.stdout, techniques: [] };
59
+ applyAnsi(state, compaction);
60
+ if (compaction.filterBuildOutput)
61
+ applyNullable(state, filterBuildOutput(state.text, command), 'build');
62
+ if (compaction.aggregateTestOutput)
63
+ applyNullable(state, aggregateTestOutput(state.text, command), 'test');
64
+ if (compaction.compactGitOutput)
65
+ applyNullable(state, compactGitOutput(state.text, command), 'git');
66
+ if (compaction.aggregateLinterOutput)
67
+ applyNullable(state, aggregateLinterOutput(state.text, command), 'linter');
68
+ let stdout = state.text;
69
+ let stderr = parts.stderr;
70
+ if (stderr !== undefined && compaction.stripAnsi)
71
+ stderr = stripAnsi(stderr);
72
+ const render = () => renderBashResult({
73
+ empty: false,
74
+ stdout,
75
+ ...(stderr === undefined ? {} : { stderr }),
76
+ markers: parts.markers,
77
+ });
78
+ let rendered = render();
79
+ if (compaction.truncate.enabled && rendered.length > compaction.truncate.maxChars) {
80
+ const markerOverhead = renderBashResult({ empty: false, stdout: '', markers: parts.markers }).length;
81
+ const budget = Math.max(1, compaction.truncate.maxChars - markerOverhead - 8);
82
+ // stderr keeps the smaller share: it is usually short, and when it is not,
83
+ // its head is still the diagnostic while its tail is usually a stack dump.
84
+ const stderrBudget = stderr === undefined ? 0 : Math.min(stderr.length, Math.floor(budget * 0.4));
85
+ const stdoutBudget = Math.max(1, budget - stderrBudget);
86
+ if (stdout.length > stdoutBudget)
87
+ stdout = truncate(stdout, stdoutBudget);
88
+ if (stderr !== undefined && stderr.length > stderrBudget)
89
+ stderr = truncate(stderr, stderrBudget);
90
+ rendered = render();
91
+ state.techniques.push('truncate');
92
+ }
93
+ // Compaction must never make a result bigger than the text it replaced.
94
+ if (rendered.length >= text.length)
95
+ return { text, techniques: [] };
96
+ return { text: rendered, techniques: state.techniques };
97
+ }
98
+ /**
99
+ * Whether a `read` result must stay byte-exact.
100
+ *
101
+ * Three cases: the caller asked for a narrow range (the tool already bounded
102
+ * it, and the caller will edit against what it sees), the file is short enough
103
+ * that filtering saves nothing worth the risk, or the path lives under a skill
104
+ * directory — skills are instructions the agent follows verbatim.
105
+ */
106
+ function shouldPreserveExactRead(text, args, compaction) {
107
+ if (!compaction.readCompaction.enabled)
108
+ return true;
109
+ if (args.offset !== undefined || args.limit !== undefined)
110
+ return true;
111
+ if (countLines(text) <= READ_EXACT_LINE_THRESHOLD)
112
+ return true;
113
+ if (compaction.preserveExactSkillReads && typeof args.path === 'string') {
114
+ const target = resolve(args.path);
115
+ const roots = [resolve(homedir(), '.agents', 'skills'), resolve(homedir(), '.dsh', 'skills')];
116
+ let cursor = process.cwd();
117
+ for (;;) {
118
+ roots.push(resolve(cursor, '.agents', 'skills'));
119
+ const parent = resolve(cursor, '..');
120
+ if (parent === cursor)
121
+ break;
122
+ cursor = parent;
123
+ }
124
+ if (roots.some((root) => target === root || target.startsWith(`${root}/`)))
125
+ return true;
126
+ }
127
+ return false;
128
+ }
129
+ function compactReadText(text, args, compaction) {
130
+ if (shouldPreserveExactRead(text, args, compaction))
131
+ return { text, techniques: [] };
132
+ const state = { text, techniques: [] };
133
+ applyAnsi(state, compaction);
134
+ const filePath = typeof args.path === 'string' ? args.path : '';
135
+ const language = detectLanguage(filePath);
136
+ if (compaction.sourceCodeFilteringEnabled && compaction.sourceCodeFiltering !== 'none') {
137
+ applyNullable(state, filterSourceCode(state.text, language, compaction.sourceCodeFiltering), `source:${compaction.sourceCodeFiltering}`);
138
+ }
139
+ if (compaction.smartTruncate.enabled && countLines(state.text) > compaction.smartTruncate.maxLines) {
140
+ applyNullable(state, smartTruncate(state.text, compaction.smartTruncate.maxLines), 'smart-truncate');
141
+ }
142
+ if (compaction.truncate.enabled && state.text.length > compaction.truncate.maxChars) {
143
+ applyNullable(state, truncate(state.text, compaction.truncate.maxChars), 'truncate');
144
+ }
145
+ if (state.techniques.length === 0)
146
+ return { text, techniques: [] };
147
+ // The banner is part of the returned result, so the size guard must weigh it.
148
+ // Filtering one comment line out of a short file saves less than the banner
149
+ // costs, and without this the result would come back longer than the text it
150
+ // replaced — the exact invariant the guard exists to hold.
151
+ const banner = `${READ_COMPACTION_BANNER_PREFIX} ${state.techniques.join(', ')}]`;
152
+ const rendered = `${banner}\n${state.text}`;
153
+ if (rendered.length >= text.length)
154
+ return { text, techniques: [] };
155
+ return { text: rendered, techniques: state.techniques };
156
+ }
157
+ function compactGrepText(text, compaction) {
158
+ const state = { text, techniques: [] };
159
+ applyAnsi(state, compaction);
160
+ if (compaction.groupSearchOutput)
161
+ applyNullable(state, groupSearchResults(state.text), 'search');
162
+ if (compaction.truncate.enabled && state.text.length > compaction.truncate.maxChars) {
163
+ applyNullable(state, truncate(state.text, compaction.truncate.maxChars), 'truncate');
164
+ }
165
+ if (state.text.length >= text.length)
166
+ return { text, techniques: [] };
167
+ return state;
168
+ }
169
+ /**
170
+ * Run every enabled technique over one tool result.
171
+ *
172
+ * Returns `changed: false` whenever the result would not actually shrink, so a
173
+ * caller can treat "no outcome" and "no improvement" identically and the model
174
+ * never pays for a rewrite that saved nothing.
175
+ *
176
+ * @param input - the tool name, its parsed arguments, and the content blocks.
177
+ * @param config - the resolved plugin configuration.
178
+ * @returns the replacement content and the techniques that fired.
179
+ */
180
+ export function compactToolResult(input, config) {
181
+ const compaction = config.outputCompaction;
182
+ if (!config.enabled || !compaction.enabled)
183
+ return { changed: false, techniques: [] };
184
+ if (!config.compactedTools.includes(input.toolName))
185
+ return { changed: false, techniques: [] };
186
+ const sourceContent = asArray(input.content);
187
+ if (sourceContent.length === 0)
188
+ return { changed: false, techniques: [] };
189
+ const args = asRecord(input.args);
190
+ const command = typeof args.command === 'string' ? args.command : undefined;
191
+ const techniques = new Set();
192
+ const originalChunks = [];
193
+ const compactedChunks = [];
194
+ let changed = false;
195
+ const nextContent = sourceContent.map((block) => {
196
+ if (!isTextBlock(block))
197
+ return block;
198
+ let transformed = { text: block.text, techniques: [] };
199
+ if (input.toolName === 'bash')
200
+ transformed = compactBashText(block.text, command, compaction);
201
+ else if (input.toolName === 'read')
202
+ transformed = compactReadText(block.text, args, compaction);
203
+ else if (input.toolName === 'grep')
204
+ transformed = compactGrepText(block.text, compaction);
205
+ for (const technique of transformed.techniques)
206
+ techniques.add(technique);
207
+ originalChunks.push(block.text);
208
+ compactedChunks.push(transformed.text);
209
+ if (transformed.text === block.text)
210
+ return block;
211
+ changed = true;
212
+ return { ...block, text: transformed.text };
213
+ });
214
+ if (!changed)
215
+ return { changed: false, techniques: [] };
216
+ if (originalChunks.join('\n') === NO_OUTPUT_PLACEHOLDER)
217
+ return { changed: false, techniques: [] };
218
+ const originalText = originalChunks.join('\n');
219
+ const compactedText = compactedChunks.join('\n');
220
+ const applied = Array.from(techniques);
221
+ return {
222
+ changed: true,
223
+ content: nextContent,
224
+ techniques: applied,
225
+ metadata: {
226
+ applied: true,
227
+ techniques: applied,
228
+ truncated: hasLossyTechnique(applied),
229
+ originalCharCount: originalText.length,
230
+ compactedCharCount: compactedText.length,
231
+ originalLineCount: countLines(originalText),
232
+ compactedLineCount: countLines(compactedText),
233
+ },
234
+ };
235
+ }
236
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/compact/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /** Whether the command is a linter this module understands. */
2
+ export declare function isLinterCommand(command: string | undefined | null): boolean;
3
+ /**
4
+ * Reduce linter output to counts plus the rules and files that dominate.
5
+ *
6
+ * The full issue list is usually thousands of near-identical lines; the
7
+ * distribution is what a reader acts on. Individual messages are dropped in
8
+ * favor of counts, which is lossy by design — the reader re-runs on the file
9
+ * they choose to fix.
10
+ */
11
+ export declare function aggregateLinterOutput(output: string, command: string | undefined | null): string | null;
12
+ //# sourceMappingURL=linter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../../src/compact/linter.ts"],"names":[],"mappings":"AA2BA,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAE3E;AA6CD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CA0CvG"}
@@ -0,0 +1,118 @@
1
+ import { matchesCommandPatterns, normalizeCommandForDetection } from './detect.js';
2
+ import { compactPath } from './text.js';
3
+ const LINTER_COMMAND_PATTERNS = [
4
+ /^(?:pnpm\s+)?(?:npx\s+)?eslint\b/,
5
+ /^(?:npx\s+)?prettier\b/,
6
+ /^ruff\b/,
7
+ /^pylint\b/,
8
+ /^mypy\b/,
9
+ /^flake8\b/,
10
+ /^black\b/,
11
+ /^cargo\s+clippy\b/,
12
+ /^golangci-lint\b/,
13
+ ];
14
+ const MAX_RULES = 10;
15
+ const MAX_FILES = 10;
16
+ const MAX_RULES_PER_FILE = 3;
17
+ /** Whether the command is a linter this module understands. */
18
+ export function isLinterCommand(command) {
19
+ return matchesCommandPatterns(command, LINTER_COMMAND_PATTERNS);
20
+ }
21
+ function parseIssueLine(line) {
22
+ const fileLineMatch = line.match(/^(.+):(\d+):(\d+):\s*(.+)$/);
23
+ if (fileLineMatch) {
24
+ const lineNumber = Number.parseInt(fileLineMatch[2] ?? '0', 10);
25
+ const content = fileLineMatch[4] ?? line;
26
+ return {
27
+ severity: /warning/i.test(content) ? 'WARNING' : 'ERROR',
28
+ rule: content.match(/\[(.+?)\]$/)?.[1] ?? 'unknown',
29
+ file: fileLineMatch[1] ?? 'unknown',
30
+ ...(Number.isNaN(lineNumber) ? {} : { line: lineNumber }),
31
+ message: content,
32
+ };
33
+ }
34
+ const rustMatch = line.match(/^(error|warning):\s*(.+?)\s+at\s+(.+):(\d+):(\d+)$/);
35
+ if (rustMatch) {
36
+ const lineNumber = Number.parseInt(rustMatch[4] ?? '0', 10);
37
+ return {
38
+ severity: (rustMatch[1]?.toUpperCase() ?? 'ERROR'),
39
+ rule: 'unknown',
40
+ file: rustMatch[3] ?? 'unknown',
41
+ ...(Number.isNaN(lineNumber) ? {} : { line: lineNumber }),
42
+ message: rustMatch[2] ?? line,
43
+ };
44
+ }
45
+ return null;
46
+ }
47
+ function detectLinterType(command) {
48
+ const normalized = normalizeCommandForDetection(command);
49
+ if (!normalized)
50
+ return 'Linter';
51
+ if (/(?:^|\s)eslint\b/.test(normalized))
52
+ return 'ESLint';
53
+ if (/^ruff\b/.test(normalized))
54
+ return 'Ruff';
55
+ if (/^pylint\b/.test(normalized))
56
+ return 'Pylint';
57
+ if (/^mypy\b/.test(normalized))
58
+ return 'MyPy';
59
+ if (/^flake8\b/.test(normalized))
60
+ return 'Flake8';
61
+ if (/clippy\b/.test(normalized))
62
+ return 'Clippy';
63
+ if (/^golangci-lint\b/.test(normalized))
64
+ return 'GolangCI-Lint';
65
+ if (/prettier\b/.test(normalized))
66
+ return 'Prettier';
67
+ return 'Linter';
68
+ }
69
+ /**
70
+ * Reduce linter output to counts plus the rules and files that dominate.
71
+ *
72
+ * The full issue list is usually thousands of near-identical lines; the
73
+ * distribution is what a reader acts on. Individual messages are dropped in
74
+ * favor of counts, which is lossy by design — the reader re-runs on the file
75
+ * they choose to fix.
76
+ */
77
+ export function aggregateLinterOutput(output, command) {
78
+ if (!isLinterCommand(command))
79
+ return null;
80
+ const linterType = detectLinterType(command);
81
+ const issues = [];
82
+ for (const line of output.split('\n')) {
83
+ const issue = parseIssueLine(line);
84
+ if (issue)
85
+ issues.push(issue);
86
+ }
87
+ if (issues.length === 0)
88
+ return `[OK] ${linterType}: No issues found`;
89
+ const errors = issues.filter((issue) => issue.severity === 'ERROR').length;
90
+ const warnings = issues.filter((issue) => issue.severity === 'WARNING').length;
91
+ const byRule = new Map();
92
+ const byFile = new Map();
93
+ for (const issue of issues) {
94
+ byRule.set(issue.rule, (byRule.get(issue.rule) ?? 0) + 1);
95
+ const existing = byFile.get(issue.file) ?? [];
96
+ existing.push(issue);
97
+ byFile.set(issue.file, existing);
98
+ }
99
+ let result = `${linterType}: ${errors} errors, ${warnings} warnings in ${byFile.size} files\n`;
100
+ result += '═══════════════════════════════════════\n';
101
+ result += 'Top rules:\n';
102
+ for (const [rule, count] of Array.from(byRule.entries()).sort((left, right) => right[1] - left[1]).slice(0, MAX_RULES)) {
103
+ result += ` ${rule} (${count}x)\n`;
104
+ }
105
+ result += '\nTop files:\n';
106
+ const sortedFiles = Array.from(byFile.entries()).sort((left, right) => right[1].length - left[1].length).slice(0, MAX_FILES);
107
+ for (const [file, fileIssues] of sortedFiles) {
108
+ result += ` ${compactPath(file, 40)} (${fileIssues.length} issues)\n`;
109
+ const fileRules = new Map();
110
+ for (const issue of fileIssues)
111
+ fileRules.set(issue.rule, (fileRules.get(issue.rule) ?? 0) + 1);
112
+ const topRules = Array.from(fileRules.entries()).sort((left, right) => right[1] - left[1]).slice(0, MAX_RULES_PER_FILE);
113
+ for (const [rule, count] of topRules)
114
+ result += ` ${rule} (${count})\n`;
115
+ }
116
+ return result;
117
+ }
118
+ //# sourceMappingURL=linter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.js","sourceRoot":"","sources":["../../src/compact/linter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Group `grep`-style matches by file.
3
+ *
4
+ * Returns `null` when the output is not recognizably a match list. The format
5
+ * check is what keeps this safe across search tools: ripgrep, GNU grep, and the
6
+ * harness's own `grep` all print `path:line:text`, but a command whose name
7
+ * merely resembles a search may print anything at all, and summarizing that
8
+ * would silently discard real output. Erring toward `null` leaves the text
9
+ * untouched, which is always correct.
10
+ *
11
+ * @param output - raw search output.
12
+ * @param maxResults - cap on rendered match lines across all files.
13
+ * @returns the grouped summary, or `null` when the shape does not match.
14
+ */
15
+ export declare function groupSearchResults(output: string, maxResults?: number): string | null;
16
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/compact/search.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,MAAM,GAAG,IAAI,CA+CjF"}
@@ -0,0 +1,67 @@
1
+ import { compactPath } from './text.js';
2
+ /** Minimum share of non-blank lines that must parse as `file:line:content`. */
3
+ const MIN_MATCH_RATIO = 0.6;
4
+ /**
5
+ * Group `grep`-style matches by file.
6
+ *
7
+ * Returns `null` when the output is not recognizably a match list. The format
8
+ * check is what keeps this safe across search tools: ripgrep, GNU grep, and the
9
+ * harness's own `grep` all print `path:line:text`, but a command whose name
10
+ * merely resembles a search may print anything at all, and summarizing that
11
+ * would silently discard real output. Erring toward `null` leaves the text
12
+ * untouched, which is always correct.
13
+ *
14
+ * @param output - raw search output.
15
+ * @param maxResults - cap on rendered match lines across all files.
16
+ * @returns the grouped summary, or `null` when the shape does not match.
17
+ */
18
+ export function groupSearchResults(output, maxResults = 50) {
19
+ const lines = output.split('\n').filter((line) => line.trim().length > 0);
20
+ if (lines.length === 0)
21
+ return null;
22
+ const results = [];
23
+ let parseable = 0;
24
+ for (const line of lines) {
25
+ const match = line.match(/^(.+?):(\d+)?:(.*)$/);
26
+ if (!match)
27
+ continue;
28
+ parseable++;
29
+ results.push({
30
+ file: match[1] ?? 'unknown',
31
+ lineNumber: match[2] ?? '?',
32
+ content: match[3] ?? '',
33
+ });
34
+ }
35
+ if (results.length === 0)
36
+ return null;
37
+ if (parseable / lines.length < MIN_MATCH_RATIO)
38
+ return null;
39
+ const byFile = new Map();
40
+ for (const result of results) {
41
+ const existing = byFile.get(result.file) ?? [];
42
+ existing.push(result);
43
+ byFile.set(result.file, existing);
44
+ }
45
+ let outputText = `${results.length} matches in ${byFile.size} files:\n\n`;
46
+ const sortedFiles = Array.from(byFile.entries()).sort((left, right) => left[0].localeCompare(right[0]));
47
+ let shown = 0;
48
+ for (const [file, matches] of sortedFiles) {
49
+ if (shown >= maxResults)
50
+ break;
51
+ outputText += `> ${compactPath(file, 50)} (${matches.length} matches):\n`;
52
+ for (const match of matches.slice(0, 10)) {
53
+ let cleaned = match.content.trim();
54
+ if (cleaned.length > 70)
55
+ cleaned = `${cleaned.slice(0, 67)}...`;
56
+ outputText += ` ${match.lineNumber}: ${cleaned}\n`;
57
+ shown++;
58
+ }
59
+ if (matches.length > 10)
60
+ outputText += ` +${matches.length - 10} more\n`;
61
+ outputText += '\n';
62
+ }
63
+ if (results.length > shown)
64
+ outputText += `... +${results.length - shown} more\n`;
65
+ return outputText;
66
+ }
67
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/compact/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { SourceCodeFilteringLevel } from '../config.js';
2
+ /** A language whose comment syntax this module knows. */
3
+ export type Language = 'javascript' | 'python' | 'rust' | 'go' | 'java' | 'c' | 'shell' | 'ruby' | 'unknown';
4
+ /** Infer a source language from a file extension. */
5
+ export declare function detectLanguage(filePath: string): Language;
6
+ /**
7
+ * Keep the most informative `maxLines` lines of a source read.
8
+ *
9
+ * The first half of the budget is kept verbatim, after which only structural
10
+ * lines survive — signatures, imports, and lone braces — with a marker where
11
+ * body lines were dropped. This preserves the shape of the file (what is
12
+ * defined where) rather than an arbitrary prefix, which is what makes the
13
+ * result still usable for orientation.
14
+ *
15
+ * @param content - file text.
16
+ * @param maxLines - line budget.
17
+ * @returns the reduced text.
18
+ */
19
+ export declare function smartTruncate(content: string, maxLines: number): string;
20
+ /** Apply the configured source-filtering level. */
21
+ export declare function filterSourceCode(content: string, language: Language, level: SourceCodeFilteringLevel): string;
22
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/compact/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAE5D,yDAAyD;AACzD,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAoD5G,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAIzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqCvE;AAuHD,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,wBAAwB,GAAG,MAAM,CAW7G"}