@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
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
+ }