@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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/README.zh.md +211 -0
- package/THIRD_PARTY_NOTICES.md +51 -0
- package/docs/assets/how-it-works.svg +47 -0
- package/docs/verification.md +279 -0
- package/lib/command.d.ts +41 -0
- package/lib/command.d.ts.map +1 -0
- package/lib/command.js +137 -0
- package/lib/command.js.map +1 -0
- package/lib/compact/build.d.ts +15 -0
- package/lib/compact/build.d.ts.map +1 -0
- package/lib/compact/build.js +140 -0
- package/lib/compact/build.js.map +1 -0
- package/lib/compact/detect.d.ts +22 -0
- package/lib/compact/detect.d.ts.map +1 -0
- package/lib/compact/detect.js +54 -0
- package/lib/compact/detect.js.map +1 -0
- package/lib/compact/dsh-result.d.ts +49 -0
- package/lib/compact/dsh-result.d.ts.map +1 -0
- package/lib/compact/dsh-result.js +84 -0
- package/lib/compact/dsh-result.js.map +1 -0
- package/lib/compact/git.d.ts +21 -0
- package/lib/compact/git.d.ts.map +1 -0
- package/lib/compact/git.js +197 -0
- package/lib/compact/git.js.map +1 -0
- package/lib/compact/index.d.ts +39 -0
- package/lib/compact/index.d.ts.map +1 -0
- package/lib/compact/index.js +236 -0
- package/lib/compact/index.js.map +1 -0
- package/lib/compact/linter.d.ts +12 -0
- package/lib/compact/linter.d.ts.map +1 -0
- package/lib/compact/linter.js +118 -0
- package/lib/compact/linter.js.map +1 -0
- package/lib/compact/search.d.ts +16 -0
- package/lib/compact/search.d.ts.map +1 -0
- package/lib/compact/search.js +67 -0
- package/lib/compact/search.js.map +1 -0
- package/lib/compact/source.d.ts +22 -0
- package/lib/compact/source.d.ts.map +1 -0
- package/lib/compact/source.js +224 -0
- package/lib/compact/source.js.map +1 -0
- package/lib/compact/test-output.d.ts +12 -0
- package/lib/compact/test-output.d.ts.map +1 -0
- package/lib/compact/test-output.js +168 -0
- package/lib/compact/test-output.js.map +1 -0
- package/lib/compact/text.d.ts +22 -0
- package/lib/compact/text.d.ts.map +1 -0
- package/lib/compact/text.js +87 -0
- package/lib/compact/text.js.map +1 -0
- package/lib/config.d.ts +243 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +183 -0
- package/lib/config.js.map +1 -0
- package/lib/index.d.ts +46 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +405 -0
- package/lib/index.js.map +1 -0
- package/lib/metrics.d.ts +35 -0
- package/lib/metrics.d.ts.map +1 -0
- package/lib/metrics.js +51 -0
- package/lib/metrics.js.map +1 -0
- package/lib/rtk-executable.d.ts +51 -0
- package/lib/rtk-executable.d.ts.map +1 -0
- package/lib/rtk-executable.js +75 -0
- package/lib/rtk-executable.js.map +1 -0
- package/lib/rtk-rewrite.d.ts +88 -0
- package/lib/rtk-rewrite.d.ts.map +1 -0
- package/lib/rtk-rewrite.js +150 -0
- package/lib/rtk-rewrite.js.map +1 -0
- package/lib/runtime-guard.d.ts +31 -0
- package/lib/runtime-guard.d.ts.map +1 -0
- package/lib/runtime-guard.js +32 -0
- package/lib/runtime-guard.js.map +1 -0
- package/package.json +82 -0
- package/scripts/link-dsh.mjs +135 -0
- package/src/command.ts +174 -0
- package/src/compact/build.ts +154 -0
- package/src/compact/detect.ts +54 -0
- package/src/compact/dsh-result.ts +99 -0
- package/src/compact/git.ts +209 -0
- package/src/compact/index.ts +284 -0
- package/src/compact/linter.ts +126 -0
- package/src/compact/search.ts +73 -0
- package/src/compact/source.ts +244 -0
- package/src/compact/test-output.ts +184 -0
- package/src/compact/text.ts +86 -0
- package/src/config.ts +263 -0
- package/src/index.ts +431 -0
- package/src/metrics.ts +84 -0
- package/src/rtk-executable.ts +117 -0
- package/src/rtk-rewrite.ts +179 -0
- package/src/runtime-guard.ts +44 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import z from '@deepseek-ai/schemastery'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How bash commands are handled.
|
|
5
|
+
*
|
|
6
|
+
* - `rewrite` — a command with an RTK equivalent is replaced before it runs.
|
|
7
|
+
* - `suggest` — the command runs unchanged; the rewrite is only reported.
|
|
8
|
+
*/
|
|
9
|
+
export type RtkMode = 'rewrite' | 'suggest'
|
|
10
|
+
|
|
11
|
+
/** Source-code filtering strength applied to `read` output. */
|
|
12
|
+
export type SourceCodeFilteringLevel = 'none' | 'minimal' | 'aggressive'
|
|
13
|
+
|
|
14
|
+
/** Line-based truncation for `read` output. */
|
|
15
|
+
export interface SmartTruncateConfig {
|
|
16
|
+
enabled: boolean
|
|
17
|
+
maxLines: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Final character-budget enforcement applied to every compacted text. */
|
|
21
|
+
export interface TruncateConfig {
|
|
22
|
+
enabled: boolean
|
|
23
|
+
maxChars: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Lossy compaction for `read` output; off by default so code reads stay exact. */
|
|
27
|
+
export interface ReadCompactionConfig {
|
|
28
|
+
enabled: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The output-compaction pipeline's switches. */
|
|
32
|
+
export interface OutputCompactionConfig {
|
|
33
|
+
enabled: boolean
|
|
34
|
+
stripAnsi: boolean
|
|
35
|
+
readCompaction: ReadCompactionConfig
|
|
36
|
+
sourceCodeFilteringEnabled: boolean
|
|
37
|
+
preserveExactSkillReads: boolean
|
|
38
|
+
sourceCodeFiltering: SourceCodeFilteringLevel
|
|
39
|
+
aggregateTestOutput: boolean
|
|
40
|
+
filterBuildOutput: boolean
|
|
41
|
+
compactGitOutput: boolean
|
|
42
|
+
aggregateLinterOutput: boolean
|
|
43
|
+
groupSearchOutput: boolean
|
|
44
|
+
trackSavings: boolean
|
|
45
|
+
/**
|
|
46
|
+
* Yield oversized results to the harness's own spill policy when it is
|
|
47
|
+
* mounted, instead of hard-truncating them first.
|
|
48
|
+
*/
|
|
49
|
+
deferToHarnessSpill: boolean
|
|
50
|
+
smartTruncate: SmartTruncateConfig
|
|
51
|
+
truncate: TruncateConfig
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The plugin's fully-resolved configuration. */
|
|
55
|
+
export interface RtkConfig {
|
|
56
|
+
enabled: boolean
|
|
57
|
+
mode: RtkMode
|
|
58
|
+
guardWhenRtkMissing: boolean
|
|
59
|
+
showRewriteNotifications: boolean
|
|
60
|
+
/** Whether a session is told once that rewriting is off because rtk is absent. */
|
|
61
|
+
notifyWhenRtkMissing: boolean
|
|
62
|
+
/** Executable used for both `--version` probes and `rtk rewrite`. */
|
|
63
|
+
rtkExecutable: string
|
|
64
|
+
/** Deadline for one `rtk rewrite` call. */
|
|
65
|
+
rewriteTimeoutMs: number
|
|
66
|
+
/** Tools whose results pass through the compaction pipeline. */
|
|
67
|
+
compactedTools: string[]
|
|
68
|
+
outputCompaction: OutputCompactionConfig
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Defaults for every field; also the fallback when a layer omits one. */
|
|
72
|
+
export const DEFAULT_CONFIG: RtkConfig = {
|
|
73
|
+
enabled: true,
|
|
74
|
+
mode: 'rewrite',
|
|
75
|
+
guardWhenRtkMissing: true,
|
|
76
|
+
// Deliberately off, unlike pi-rtk-optimizer's equivalent flag. There the
|
|
77
|
+
// notice is a TUI toast; here it is appended to the tool result, so it would
|
|
78
|
+
// sit in the model's context on every rewritten call. The saving is the
|
|
79
|
+
// point of the plugin, and `/rtk stats` already reports what happened.
|
|
80
|
+
showRewriteNotifications: false,
|
|
81
|
+
// On: a missing binary is the one condition that silently costs the user the
|
|
82
|
+
// whole feature. The notice is emitted once per session, so its cost is
|
|
83
|
+
// bounded even though it is on by default.
|
|
84
|
+
notifyWhenRtkMissing: true,
|
|
85
|
+
rtkExecutable: 'rtk',
|
|
86
|
+
rewriteTimeoutMs: 3000,
|
|
87
|
+
compactedTools: ['bash', 'read', 'grep'],
|
|
88
|
+
outputCompaction: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
stripAnsi: true,
|
|
91
|
+
readCompaction: { enabled: false },
|
|
92
|
+
sourceCodeFilteringEnabled: false,
|
|
93
|
+
preserveExactSkillReads: false,
|
|
94
|
+
sourceCodeFiltering: 'none',
|
|
95
|
+
aggregateTestOutput: true,
|
|
96
|
+
filterBuildOutput: true,
|
|
97
|
+
compactGitOutput: true,
|
|
98
|
+
aggregateLinterOutput: true,
|
|
99
|
+
groupSearchOutput: true,
|
|
100
|
+
trackSavings: true,
|
|
101
|
+
// The harness spill policy truncates recoverably (full output on disk,
|
|
102
|
+
// head/tail preview inline); this plugin's hard truncation does not. It
|
|
103
|
+
// also runs first, so at 12 000 characters it fires far below spill's
|
|
104
|
+
// threshold and spill never sees a large result. Deferring is the default;
|
|
105
|
+
// `false` keeps this plugin's own bound even when spill is mounted.
|
|
106
|
+
deferToHarnessSpill: true,
|
|
107
|
+
smartTruncate: { enabled: false, maxLines: 220 },
|
|
108
|
+
truncate: { enabled: true, maxChars: 12000 },
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const SOURCE_CODE_FILTERING_LEVELS: readonly SourceCodeFilteringLevel[] = ['none', 'minimal', 'aggressive']
|
|
113
|
+
|
|
114
|
+
/** Bounds published in the configuration catalog and enforced during normalization. */
|
|
115
|
+
export const BOUNDS = {
|
|
116
|
+
maxLines: { min: 40, max: 4000 },
|
|
117
|
+
maxChars: { min: 1000, max: 200000 },
|
|
118
|
+
} as const
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The composition-facing configuration schema.
|
|
122
|
+
*
|
|
123
|
+
* Exported as both a value (the schemastery schema the loader validates and
|
|
124
|
+
* fills defaults with) and, through declaration merging, the resolved type.
|
|
125
|
+
* No explicit annotation is attached: `z.object` already infers the pair, and
|
|
126
|
+
* a hand-written one would have to restate every nested field.
|
|
127
|
+
*/
|
|
128
|
+
export const Config = z.object({
|
|
129
|
+
enabled: z.boolean().default(DEFAULT_CONFIG.enabled).description('Master switch for command rewriting and output compaction.'),
|
|
130
|
+
mode: z
|
|
131
|
+
.union([z.const('rewrite'), z.const('suggest')])
|
|
132
|
+
.default(DEFAULT_CONFIG.mode)
|
|
133
|
+
.description('`rewrite` replaces a supported command; `suggest` only reports the equivalent.'),
|
|
134
|
+
guardWhenRtkMissing: z
|
|
135
|
+
.boolean()
|
|
136
|
+
.default(DEFAULT_CONFIG.guardWhenRtkMissing)
|
|
137
|
+
.description('Run the original command unchanged when the rtk executable is unavailable.'),
|
|
138
|
+
showRewriteNotifications: z
|
|
139
|
+
.boolean()
|
|
140
|
+
.default(DEFAULT_CONFIG.showRewriteNotifications)
|
|
141
|
+
.description('Record rewrite decisions so they are visible in the session log.'),
|
|
142
|
+
notifyWhenRtkMissing: z
|
|
143
|
+
.boolean()
|
|
144
|
+
.default(DEFAULT_CONFIG.notifyWhenRtkMissing)
|
|
145
|
+
.description('Tell each session once that command rewriting is off because rtk is not installed.'),
|
|
146
|
+
rtkExecutable: z.string().default(DEFAULT_CONFIG.rtkExecutable).description('Executable name or path for rtk.'),
|
|
147
|
+
rewriteTimeoutMs: z.natural().default(DEFAULT_CONFIG.rewriteTimeoutMs).description('Deadline in milliseconds for one `rtk rewrite` call.'),
|
|
148
|
+
compactedTools: z
|
|
149
|
+
.array(z.string())
|
|
150
|
+
.default([...DEFAULT_CONFIG.compactedTools])
|
|
151
|
+
.description('Tool names whose text results pass through the compaction pipeline.'),
|
|
152
|
+
outputCompaction: z
|
|
153
|
+
.object({
|
|
154
|
+
enabled: z.boolean().default(true),
|
|
155
|
+
stripAnsi: z.boolean().default(true),
|
|
156
|
+
readCompaction: z.object({ enabled: z.boolean().default(false) }),
|
|
157
|
+
sourceCodeFilteringEnabled: z.boolean().default(false),
|
|
158
|
+
preserveExactSkillReads: z.boolean().default(false),
|
|
159
|
+
sourceCodeFiltering: z
|
|
160
|
+
.union([z.const('none'), z.const('minimal'), z.const('aggressive')])
|
|
161
|
+
.default(DEFAULT_CONFIG.outputCompaction.sourceCodeFiltering),
|
|
162
|
+
aggregateTestOutput: z.boolean().default(true),
|
|
163
|
+
filterBuildOutput: z.boolean().default(true),
|
|
164
|
+
compactGitOutput: z.boolean().default(true),
|
|
165
|
+
aggregateLinterOutput: z.boolean().default(true),
|
|
166
|
+
groupSearchOutput: z.boolean().default(true),
|
|
167
|
+
trackSavings: z.boolean().default(true),
|
|
168
|
+
deferToHarnessSpill: z.boolean().default(true),
|
|
169
|
+
smartTruncate: z.object({
|
|
170
|
+
enabled: z.boolean().default(false),
|
|
171
|
+
maxLines: z.natural().default(DEFAULT_CONFIG.outputCompaction.smartTruncate.maxLines),
|
|
172
|
+
}),
|
|
173
|
+
truncate: z.object({
|
|
174
|
+
enabled: z.boolean().default(true),
|
|
175
|
+
maxChars: z.natural().default(DEFAULT_CONFIG.outputCompaction.truncate.maxChars),
|
|
176
|
+
}),
|
|
177
|
+
})
|
|
178
|
+
.default(DEFAULT_CONFIG.outputCompaction),
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
182
|
+
return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function pickBoolean(value: unknown, fallback: boolean): boolean {
|
|
186
|
+
return typeof value === 'boolean' ? value : fallback
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function pickNumber(value: unknown, fallback: number, min: number, max: number): number {
|
|
190
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return fallback
|
|
191
|
+
return Math.min(max, Math.max(min, Math.floor(value)))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function pickString(value: unknown, fallback: string): string {
|
|
195
|
+
return typeof value === 'string' && value.trim() ? value : fallback
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function pickStringList(value: unknown, fallback: readonly string[]): string[] {
|
|
199
|
+
if (!Array.isArray(value)) return [...fallback]
|
|
200
|
+
const names = value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
|
|
201
|
+
return names.length > 0 ? names : [...fallback]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Normalize arbitrary configuration into a complete {@link RtkConfig}.
|
|
206
|
+
*
|
|
207
|
+
* The loader already validates the composition's `config:` block, but this
|
|
208
|
+
* entry is also reachable from tests, from `apply()` called directly, and from
|
|
209
|
+
* a runtime patch, so every field is re-derived rather than trusted. Numbers
|
|
210
|
+
* are clamped to {@link BOUNDS} so a hand-edited value cannot disable the
|
|
211
|
+
* pipeline's safeguards by accident.
|
|
212
|
+
*/
|
|
213
|
+
export function normalizeConfig(raw: unknown): RtkConfig {
|
|
214
|
+
const root = asRecord(raw)
|
|
215
|
+
const compaction = asRecord(root.outputCompaction)
|
|
216
|
+
const readCompaction = asRecord(compaction.readCompaction)
|
|
217
|
+
const smartTruncate = asRecord(compaction.smartTruncate)
|
|
218
|
+
const truncate = asRecord(compaction.truncate)
|
|
219
|
+
|
|
220
|
+
const mode = root.mode === 'suggest' ? 'suggest' : 'rewrite'
|
|
221
|
+
const filtering = SOURCE_CODE_FILTERING_LEVELS.includes(compaction.sourceCodeFiltering as SourceCodeFilteringLevel)
|
|
222
|
+
? (compaction.sourceCodeFiltering as SourceCodeFilteringLevel)
|
|
223
|
+
: DEFAULT_CONFIG.outputCompaction.sourceCodeFiltering
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
enabled: pickBoolean(root.enabled, DEFAULT_CONFIG.enabled),
|
|
227
|
+
mode,
|
|
228
|
+
guardWhenRtkMissing: pickBoolean(root.guardWhenRtkMissing, DEFAULT_CONFIG.guardWhenRtkMissing),
|
|
229
|
+
showRewriteNotifications: pickBoolean(root.showRewriteNotifications, DEFAULT_CONFIG.showRewriteNotifications),
|
|
230
|
+
notifyWhenRtkMissing: pickBoolean(root.notifyWhenRtkMissing, DEFAULT_CONFIG.notifyWhenRtkMissing),
|
|
231
|
+
rtkExecutable: pickString(root.rtkExecutable, DEFAULT_CONFIG.rtkExecutable),
|
|
232
|
+
rewriteTimeoutMs: pickNumber(root.rewriteTimeoutMs, DEFAULT_CONFIG.rewriteTimeoutMs, 100, 60000),
|
|
233
|
+
compactedTools: pickStringList(root.compactedTools, DEFAULT_CONFIG.compactedTools),
|
|
234
|
+
outputCompaction: {
|
|
235
|
+
enabled: pickBoolean(compaction.enabled, DEFAULT_CONFIG.outputCompaction.enabled),
|
|
236
|
+
stripAnsi: pickBoolean(compaction.stripAnsi, DEFAULT_CONFIG.outputCompaction.stripAnsi),
|
|
237
|
+
readCompaction: {
|
|
238
|
+
enabled: pickBoolean(readCompaction.enabled, DEFAULT_CONFIG.outputCompaction.readCompaction.enabled),
|
|
239
|
+
},
|
|
240
|
+
sourceCodeFilteringEnabled: pickBoolean(
|
|
241
|
+
compaction.sourceCodeFilteringEnabled,
|
|
242
|
+
DEFAULT_CONFIG.outputCompaction.sourceCodeFilteringEnabled,
|
|
243
|
+
),
|
|
244
|
+
preserveExactSkillReads: pickBoolean(compaction.preserveExactSkillReads, DEFAULT_CONFIG.outputCompaction.preserveExactSkillReads),
|
|
245
|
+
sourceCodeFiltering: filtering,
|
|
246
|
+
aggregateTestOutput: pickBoolean(compaction.aggregateTestOutput, DEFAULT_CONFIG.outputCompaction.aggregateTestOutput),
|
|
247
|
+
filterBuildOutput: pickBoolean(compaction.filterBuildOutput, DEFAULT_CONFIG.outputCompaction.filterBuildOutput),
|
|
248
|
+
compactGitOutput: pickBoolean(compaction.compactGitOutput, DEFAULT_CONFIG.outputCompaction.compactGitOutput),
|
|
249
|
+
aggregateLinterOutput: pickBoolean(compaction.aggregateLinterOutput, DEFAULT_CONFIG.outputCompaction.aggregateLinterOutput),
|
|
250
|
+
groupSearchOutput: pickBoolean(compaction.groupSearchOutput, DEFAULT_CONFIG.outputCompaction.groupSearchOutput),
|
|
251
|
+
trackSavings: pickBoolean(compaction.trackSavings, DEFAULT_CONFIG.outputCompaction.trackSavings),
|
|
252
|
+
deferToHarnessSpill: pickBoolean(compaction.deferToHarnessSpill, DEFAULT_CONFIG.outputCompaction.deferToHarnessSpill),
|
|
253
|
+
smartTruncate: {
|
|
254
|
+
enabled: pickBoolean(smartTruncate.enabled, DEFAULT_CONFIG.outputCompaction.smartTruncate.enabled),
|
|
255
|
+
maxLines: pickNumber(smartTruncate.maxLines, DEFAULT_CONFIG.outputCompaction.smartTruncate.maxLines, BOUNDS.maxLines.min, BOUNDS.maxLines.max),
|
|
256
|
+
},
|
|
257
|
+
truncate: {
|
|
258
|
+
enabled: pickBoolean(truncate.enabled, DEFAULT_CONFIG.outputCompaction.truncate.enabled),
|
|
259
|
+
maxChars: pickNumber(truncate.maxChars, DEFAULT_CONFIG.outputCompaction.truncate.maxChars, BOUNDS.maxChars.min, BOUNDS.maxChars.max),
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
}
|