harnesstrim 0.0.7 → 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/assets/adapter-hermes/plugin/__init__.py +1 -0
- package/dist/cli.mjs +150 -41
- package/package.json +12 -10
- package/LICENSE +0 -21
package/dist/cli.mjs
CHANGED
|
@@ -724,6 +724,7 @@ function makeTrimEvent(partial2) {
|
|
|
724
724
|
reducer: partial2.reducer,
|
|
725
725
|
beforeChars: partial2.beforeChars,
|
|
726
726
|
afterChars: partial2.afterChars,
|
|
727
|
+
changed: partial2.changed ?? true,
|
|
727
728
|
beforeTokens: partial2.beforeTokens ?? null,
|
|
728
729
|
afterTokens: partial2.afterTokens ?? null
|
|
729
730
|
};
|
|
@@ -735,10 +736,30 @@ function pct(before, after) {
|
|
|
735
736
|
function summarize(events) {
|
|
736
737
|
let beforeChars = 0;
|
|
737
738
|
let afterChars = 0;
|
|
739
|
+
let reduced = 0;
|
|
740
|
+
let passThrough = 0;
|
|
741
|
+
let reductionErrors = 0;
|
|
742
|
+
let grewChars = 0;
|
|
738
743
|
const byReducerMap = /* @__PURE__ */ new Map();
|
|
744
|
+
const byHarnessMap = /* @__PURE__ */ new Map();
|
|
739
745
|
for (const e of events) {
|
|
740
746
|
beforeChars += e.beforeChars;
|
|
741
747
|
afterChars += e.afterChars;
|
|
748
|
+
if (e.changed === false) {
|
|
749
|
+
passThrough++;
|
|
750
|
+
} else if (e.afterChars > e.beforeChars) {
|
|
751
|
+
reductionErrors++;
|
|
752
|
+
grewChars += e.afterChars - e.beforeChars;
|
|
753
|
+
} else {
|
|
754
|
+
reduced++;
|
|
755
|
+
}
|
|
756
|
+
const harness = e.harness ?? "unknown";
|
|
757
|
+
const h = byHarnessMap.get(harness) ?? { harness, count: 0, beforeChars: 0, afterChars: 0, savedChars: 0, reductionPct: 0 };
|
|
758
|
+
h.count += 1;
|
|
759
|
+
h.beforeChars += e.beforeChars;
|
|
760
|
+
h.afterChars += e.afterChars;
|
|
761
|
+
h.savedChars += e.beforeChars - e.afterChars;
|
|
762
|
+
byHarnessMap.set(harness, h);
|
|
742
763
|
if (e.reducer === null) continue;
|
|
743
764
|
const b = byReducerMap.get(e.reducer) ?? { reducer: e.reducer, count: 0, beforeChars: 0, afterChars: 0, savedChars: 0 };
|
|
744
765
|
b.count += 1;
|
|
@@ -748,13 +769,20 @@ function summarize(events) {
|
|
|
748
769
|
byReducerMap.set(e.reducer, b);
|
|
749
770
|
}
|
|
750
771
|
const byReducer = [...byReducerMap.values()].sort((a, b) => b.savedChars - a.savedChars);
|
|
772
|
+
const byHarness = [...byHarnessMap.values()].map((h) => ({ ...h, reductionPct: pct(h.beforeChars, h.afterChars) })).sort((a, b) => b.savedChars - a.savedChars);
|
|
751
773
|
return {
|
|
752
774
|
events: events.length,
|
|
753
775
|
beforeChars,
|
|
754
776
|
afterChars,
|
|
755
777
|
savedChars: beforeChars - afterChars,
|
|
756
778
|
reductionPct: pct(beforeChars, afterChars),
|
|
757
|
-
byReducer
|
|
779
|
+
byReducer,
|
|
780
|
+
byHarness,
|
|
781
|
+
reduced,
|
|
782
|
+
passThrough,
|
|
783
|
+
passThroughRate: events.length === 0 ? 0 : Math.round(passThrough / events.length * 1e3) / 10,
|
|
784
|
+
reductionErrors,
|
|
785
|
+
grewChars
|
|
758
786
|
};
|
|
759
787
|
}
|
|
760
788
|
function parseTrimEvents(jsonl) {
|
|
@@ -787,6 +815,7 @@ function normalize(v) {
|
|
|
787
815
|
reducer: v.reducer,
|
|
788
816
|
beforeChars: v.beforeChars,
|
|
789
817
|
afterChars: v.afterChars,
|
|
818
|
+
changed: typeof v.changed === "boolean" ? v.changed : true,
|
|
790
819
|
beforeTokens: typeof v.beforeTokens === "number" ? v.beforeTokens : null,
|
|
791
820
|
afterTokens: typeof v.afterTokens === "number" ? v.afterTokens : null
|
|
792
821
|
};
|
|
@@ -22675,8 +22704,9 @@ function createFileSink(metricsPath) {
|
|
|
22675
22704
|
}
|
|
22676
22705
|
};
|
|
22677
22706
|
}
|
|
22678
|
-
function runReduceTool(text, minLength, sink = noopSink) {
|
|
22707
|
+
function runReduceTool(text, minLength, sink = noopSink, trackPassThrough2 = true) {
|
|
22679
22708
|
const result = reduceAuto(text, minLength);
|
|
22709
|
+
const threshold = minLength ?? DEFAULT_MIN_LENGTH;
|
|
22680
22710
|
if (result.changed) {
|
|
22681
22711
|
sink(
|
|
22682
22712
|
makeTrimEvent({
|
|
@@ -22687,11 +22717,23 @@ function runReduceTool(text, minLength, sink = noopSink) {
|
|
|
22687
22717
|
afterChars: result.output.length
|
|
22688
22718
|
})
|
|
22689
22719
|
);
|
|
22720
|
+
} else if (trackPassThrough2 && text.length >= threshold) {
|
|
22721
|
+
sink(
|
|
22722
|
+
makeTrimEvent({
|
|
22723
|
+
harness: "mcp",
|
|
22724
|
+
tool: "reduce",
|
|
22725
|
+
reducer: null,
|
|
22726
|
+
beforeChars: text.length,
|
|
22727
|
+
afterChars: text.length,
|
|
22728
|
+
changed: false
|
|
22729
|
+
})
|
|
22730
|
+
);
|
|
22690
22731
|
}
|
|
22691
22732
|
return { content: [{ type: "text", text: result.output }] };
|
|
22692
22733
|
}
|
|
22693
22734
|
function createServer(options = {}) {
|
|
22694
22735
|
const sink = options.metricsPath ? createFileSink(options.metricsPath) : noopSink;
|
|
22736
|
+
const trackPassThrough2 = options.trackPassThrough !== false;
|
|
22695
22737
|
const server = new McpServer({ name: "harnesstrim", version: "0.0.1" });
|
|
22696
22738
|
server.registerTool(
|
|
22697
22739
|
"reduce",
|
|
@@ -22703,13 +22745,15 @@ function createServer(options = {}) {
|
|
|
22703
22745
|
minLength: external_exports.number().optional().describe("Skip reduction for inputs shorter than this many characters (default 400)")
|
|
22704
22746
|
}
|
|
22705
22747
|
},
|
|
22706
|
-
async ({ text, minLength }) => runReduceTool(text, minLength, sink)
|
|
22748
|
+
async ({ text, minLength }) => runReduceTool(text, minLength, sink, trackPassThrough2)
|
|
22707
22749
|
);
|
|
22708
22750
|
return server;
|
|
22709
22751
|
}
|
|
22710
22752
|
async function startStdioServer(options = {}) {
|
|
22711
22753
|
const metricsPath = options.metricsPath ?? process.env.HARNESSTRIM_TELEMETRY_PATH;
|
|
22712
|
-
const
|
|
22754
|
+
const envTrack = process.env.HARNESSTRIM_TRACK_PASSTHROUGH;
|
|
22755
|
+
const trackPassThrough2 = options.trackPassThrough ?? (envTrack !== void 0 ? envTrack !== "0" && envTrack !== "false" : true);
|
|
22756
|
+
const server = createServer({ metricsPath, trackPassThrough: trackPassThrough2 });
|
|
22713
22757
|
const transport = new StdioServerTransport();
|
|
22714
22758
|
await server.connect(transport);
|
|
22715
22759
|
}
|
|
@@ -23042,9 +23086,15 @@ import path3 from "node:path";
|
|
|
23042
23086
|
init_src();
|
|
23043
23087
|
function reduceCodexPayload(rawJson, minLength) {
|
|
23044
23088
|
const extracted = extractToolOutput(rawJson);
|
|
23045
|
-
if (extracted === null) return { response: "{}", event: null };
|
|
23089
|
+
if (extracted === null) return { response: "{}", event: null, attempt: null };
|
|
23046
23090
|
const result = reduceAuto(extracted.output, minLength);
|
|
23047
|
-
if (!result.changed)
|
|
23091
|
+
if (!result.changed) {
|
|
23092
|
+
return {
|
|
23093
|
+
response: "{}",
|
|
23094
|
+
event: null,
|
|
23095
|
+
attempt: extracted.output.length >= (minLength ?? DEFAULT_MIN_LENGTH) ? { tool: extracted.toolName, beforeChars: extracted.output.length } : null
|
|
23096
|
+
};
|
|
23097
|
+
}
|
|
23048
23098
|
const response = JSON.stringify({
|
|
23049
23099
|
decision: "block",
|
|
23050
23100
|
reason: `HarnessTrim reduced ${extracted.toolName} output (${result.reducer}):
|
|
@@ -23058,7 +23108,8 @@ ${result.output}`
|
|
|
23058
23108
|
reducer: result.reducer,
|
|
23059
23109
|
beforeChars: extracted.output.length,
|
|
23060
23110
|
afterChars: result.output.length
|
|
23061
|
-
}
|
|
23111
|
+
},
|
|
23112
|
+
attempt: null
|
|
23062
23113
|
};
|
|
23063
23114
|
}
|
|
23064
23115
|
function extractToolOutput(rawJson) {
|
|
@@ -23319,9 +23370,15 @@ import path8 from "node:path";
|
|
|
23319
23370
|
init_src();
|
|
23320
23371
|
function reduceClaudePayload(rawJson, minLength) {
|
|
23321
23372
|
const extracted = extractToolOutput2(rawJson);
|
|
23322
|
-
if (extracted === null) return { response: "{}", event: null };
|
|
23373
|
+
if (extracted === null) return { response: "{}", event: null, attempt: null };
|
|
23323
23374
|
const result = reduceAuto(extracted.output, minLength);
|
|
23324
|
-
if (!result.changed)
|
|
23375
|
+
if (!result.changed) {
|
|
23376
|
+
return {
|
|
23377
|
+
response: "{}",
|
|
23378
|
+
event: null,
|
|
23379
|
+
attempt: extracted.output.length >= (minLength ?? DEFAULT_MIN_LENGTH) ? { tool: extracted.toolName, beforeChars: extracted.output.length } : null
|
|
23380
|
+
};
|
|
23381
|
+
}
|
|
23325
23382
|
const response = JSON.stringify({
|
|
23326
23383
|
hookSpecificOutput: {
|
|
23327
23384
|
hookEventName: "PostToolUse",
|
|
@@ -23335,7 +23392,8 @@ function reduceClaudePayload(rawJson, minLength) {
|
|
|
23335
23392
|
reducer: result.reducer,
|
|
23336
23393
|
beforeChars: extracted.output.length,
|
|
23337
23394
|
afterChars: result.output.length
|
|
23338
|
-
}
|
|
23395
|
+
},
|
|
23396
|
+
attempt: null
|
|
23339
23397
|
};
|
|
23340
23398
|
}
|
|
23341
23399
|
function extractToolOutput2(rawJson) {
|
|
@@ -23675,7 +23733,7 @@ function loadMetrics(filePath) {
|
|
|
23675
23733
|
// package.json
|
|
23676
23734
|
var package_default = {
|
|
23677
23735
|
name: "harnesstrim",
|
|
23678
|
-
version: "0.0
|
|
23736
|
+
version: "0.1.0",
|
|
23679
23737
|
description: "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
23680
23738
|
license: "MIT",
|
|
23681
23739
|
type: "module",
|
|
@@ -24284,15 +24342,28 @@ Enable it in the adapter (telemetry: true) to record reductions.`;
|
|
|
24284
24342
|
const lines = [
|
|
24285
24343
|
`harnesstrim metrics \u2014 ${result.path}`,
|
|
24286
24344
|
"",
|
|
24287
|
-
`
|
|
24288
|
-
`
|
|
24289
|
-
|
|
24290
|
-
"By reducer:"
|
|
24345
|
+
`Attempts: ${s.events} (${s.reduced} reduced, ${s.passThrough} pass-through, ${s.reductionErrors} error)`,
|
|
24346
|
+
`Pass-through: ${s.passThroughRate}% of attempts unchanged`,
|
|
24347
|
+
`Chars: ${s.beforeChars} -> ${s.afterChars} (saved ${s.savedChars}, -${s.reductionPct}%)`
|
|
24291
24348
|
];
|
|
24349
|
+
if (s.reductionErrors > 0) {
|
|
24350
|
+
lines.push(`Reduction errors: ${s.reductionErrors} attempt(s) GREW the output (+${s.grewChars} chars) \u2014 investigate`);
|
|
24351
|
+
}
|
|
24352
|
+
lines.push("");
|
|
24353
|
+
lines.push("By reducer:");
|
|
24292
24354
|
for (const b of s.byReducer) {
|
|
24293
24355
|
const p = b.beforeChars === 0 ? 0 : Math.round(b.savedChars / b.beforeChars * 1e3) / 10;
|
|
24294
24356
|
lines.push(` ${b.reducer.padEnd(20)} ${b.count}x saved ${b.savedChars} chars (-${p}%)`);
|
|
24295
24357
|
}
|
|
24358
|
+
if (s.byHarness.length > 0) {
|
|
24359
|
+
lines.push("");
|
|
24360
|
+
lines.push("By harness:");
|
|
24361
|
+
for (const h of s.byHarness) {
|
|
24362
|
+
const p = h.beforeChars === 0 ? 0 : Math.round(h.savedChars / h.beforeChars * 1e3) / 10;
|
|
24363
|
+
const sign = p < 0 ? "" : "-";
|
|
24364
|
+
lines.push(` ${h.harness.padEnd(12)} ${h.count}x saved ${h.savedChars} chars (${sign}${Math.abs(p)}%)`);
|
|
24365
|
+
}
|
|
24366
|
+
}
|
|
24296
24367
|
return lines.join("\n");
|
|
24297
24368
|
}
|
|
24298
24369
|
function renderUninstall(result, apply) {
|
|
@@ -24665,22 +24736,39 @@ async function main(argv) {
|
|
|
24665
24736
|
return 1;
|
|
24666
24737
|
}
|
|
24667
24738
|
const input = await readStdin();
|
|
24668
|
-
const { response, event } = which === "claude" ? reduceClaudePayload(input) : reduceCodexPayload(input);
|
|
24739
|
+
const { response, event, attempt } = which === "claude" ? reduceClaudePayload(input) : reduceCodexPayload(input);
|
|
24669
24740
|
process.stdout.write(response);
|
|
24670
|
-
if (values.metrics
|
|
24741
|
+
if (values.metrics) {
|
|
24671
24742
|
try {
|
|
24672
24743
|
const p = path16.resolve(values.metrics);
|
|
24673
24744
|
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24745
|
+
if (event) {
|
|
24746
|
+
fs12.appendFileSync(
|
|
24747
|
+
p,
|
|
24748
|
+
JSON.stringify(
|
|
24749
|
+
makeTrimEvent({
|
|
24750
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24751
|
+
harness: which,
|
|
24752
|
+
...event
|
|
24753
|
+
})
|
|
24754
|
+
) + "\n"
|
|
24755
|
+
);
|
|
24756
|
+
} else if (attempt && trackPassThrough()) {
|
|
24757
|
+
fs12.appendFileSync(
|
|
24758
|
+
p,
|
|
24759
|
+
JSON.stringify(
|
|
24760
|
+
makeTrimEvent({
|
|
24761
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24762
|
+
harness: which,
|
|
24763
|
+
tool: attempt.tool,
|
|
24764
|
+
reducer: null,
|
|
24765
|
+
beforeChars: attempt.beforeChars,
|
|
24766
|
+
afterChars: attempt.beforeChars,
|
|
24767
|
+
changed: false
|
|
24768
|
+
})
|
|
24769
|
+
) + "\n"
|
|
24770
|
+
);
|
|
24771
|
+
}
|
|
24684
24772
|
} catch {
|
|
24685
24773
|
}
|
|
24686
24774
|
}
|
|
@@ -24734,23 +24822,40 @@ async function main(argv) {
|
|
|
24734
24822
|
const input = await readStdin();
|
|
24735
24823
|
const result = reducePipe(input, minLength);
|
|
24736
24824
|
process.stdout.write(result.output);
|
|
24737
|
-
if (values.metrics
|
|
24825
|
+
if (values.metrics) {
|
|
24738
24826
|
try {
|
|
24739
24827
|
const p = path16.resolve(values.metrics);
|
|
24740
24828
|
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24741
|
-
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24829
|
+
if (result.changed) {
|
|
24830
|
+
fs12.appendFileSync(
|
|
24831
|
+
p,
|
|
24832
|
+
JSON.stringify(
|
|
24833
|
+
makeTrimEvent({
|
|
24834
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24835
|
+
harness: "pipe",
|
|
24836
|
+
tool: "reduce",
|
|
24837
|
+
reducer: result.reducer,
|
|
24838
|
+
beforeChars: result.beforeChars,
|
|
24839
|
+
afterChars: result.afterChars
|
|
24840
|
+
})
|
|
24841
|
+
) + "\n"
|
|
24842
|
+
);
|
|
24843
|
+
} else if (trackPassThrough() && input.length >= (minLength ?? DEFAULT_MIN_LENGTH)) {
|
|
24844
|
+
fs12.appendFileSync(
|
|
24845
|
+
p,
|
|
24846
|
+
JSON.stringify(
|
|
24847
|
+
makeTrimEvent({
|
|
24848
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24849
|
+
harness: "pipe",
|
|
24850
|
+
tool: "reduce",
|
|
24851
|
+
reducer: null,
|
|
24852
|
+
beforeChars: input.length,
|
|
24853
|
+
afterChars: input.length,
|
|
24854
|
+
changed: false
|
|
24855
|
+
})
|
|
24856
|
+
) + "\n"
|
|
24857
|
+
);
|
|
24858
|
+
}
|
|
24754
24859
|
} catch {
|
|
24755
24860
|
}
|
|
24756
24861
|
}
|
|
@@ -24796,6 +24901,10 @@ function parseModeFlag(value) {
|
|
|
24796
24901
|
function splitTools(value) {
|
|
24797
24902
|
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
24798
24903
|
}
|
|
24904
|
+
function trackPassThrough() {
|
|
24905
|
+
const v = process.env.HARNESSTRIM_TRACK_PASSTHROUGH;
|
|
24906
|
+
return v === void 0 || v !== "0" && v !== "false";
|
|
24907
|
+
}
|
|
24799
24908
|
main(process.argv.slice(2)).then((code) => {
|
|
24800
24909
|
process.exitCode = code;
|
|
24801
24910
|
}).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harnesstrim",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,18 +15,20 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"
|
|
19
|
-
"@harnesstrim/adapter-codex": "
|
|
20
|
-
"@harnesstrim/adapter-
|
|
21
|
-
"@harnesstrim/adapter-pi": "
|
|
22
|
-
"@harnesstrim/benchmarks": "
|
|
23
|
-
"@harnesstrim/
|
|
24
|
-
"@harnesstrim/
|
|
25
|
-
"
|
|
18
|
+
"@harnesstrim/adapter-claude": "workspace:*",
|
|
19
|
+
"@harnesstrim/adapter-codex": "workspace:*",
|
|
20
|
+
"@harnesstrim/adapter-hermes": "workspace:*",
|
|
21
|
+
"@harnesstrim/adapter-pi": "workspace:*",
|
|
22
|
+
"@harnesstrim/benchmarks": "workspace:*",
|
|
23
|
+
"@harnesstrim/core": "workspace:*",
|
|
24
|
+
"@harnesstrim/mcp": "workspace:*",
|
|
25
|
+
"esbuild": "^0.25.0"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "node build.mjs",
|
|
29
|
+
"prepare": "node build.mjs",
|
|
30
|
+
"prepack": "node build.mjs",
|
|
29
31
|
"test": "node --test \"src/**/*.test.ts\"",
|
|
30
32
|
"typecheck": "tsc -p tsconfig.json"
|
|
31
33
|
}
|
|
32
|
-
}
|
|
34
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 HarnessTrim contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|