harnesstrim 0.0.4 → 0.0.6
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/LICENSE +21 -0
- package/assets/adapter-pi/extension/harnesstrim.ts +30 -17
- package/dist/cli.mjs +299 -51
- package/package.json +10 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Pi fires `tool_result` after a tool finishes and before the result reaches the model;
|
|
4
4
|
// handlers chain like middleware and may return a patch ({ content, details, isError }).
|
|
5
|
-
// This extension reduces
|
|
5
|
+
// This extension reduces text chunks in structured tool results (test runners, git diffs, ...)
|
|
6
6
|
// by shelling out to `harnesstrim reduce`, so it is self-contained (no workspace imports)
|
|
7
7
|
// and loads from `~/.pi/agent/extensions/` or `<project>/.pi/extensions/`.
|
|
8
8
|
//
|
|
@@ -12,15 +12,19 @@
|
|
|
12
12
|
// HARNESSTRIM_MINLENGTH=<chars> (default 400)
|
|
13
13
|
import { spawnSync } from "node:child_process";
|
|
14
14
|
|
|
15
|
+
type TextContent = { type: "text"; text: string };
|
|
16
|
+
type ToolContent = TextContent | { type: string; [key: string]: unknown };
|
|
17
|
+
|
|
15
18
|
interface ToolResultEvent {
|
|
16
|
-
content?:
|
|
19
|
+
content?: ToolContent[];
|
|
17
20
|
isError?: boolean;
|
|
18
21
|
}
|
|
19
22
|
interface ExtensionAPI {
|
|
20
23
|
on(event: string, handler: (event: ToolResultEvent, ctx: unknown) => unknown): void;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
const
|
|
26
|
+
const runtime = globalThis as typeof globalThis & { process?: NodeJS.Process };
|
|
27
|
+
const env = runtime.process?.env ?? {};
|
|
24
28
|
const MODE = env.HARNESSTRIM_MODE ?? "dryrun";
|
|
25
29
|
const MIN_LENGTH = Number(env.HARNESSTRIM_MINLENGTH ?? "400") || 400;
|
|
26
30
|
const MARKER = "[harnesstrim";
|
|
@@ -44,19 +48,28 @@ function reduceViaCli(text: string): string | null {
|
|
|
44
48
|
export default function harnesstrim(pi: ExtensionAPI): void {
|
|
45
49
|
if (MODE === "off") return;
|
|
46
50
|
pi.on("tool_result", async (event) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
if (!Array.isArray(event.content)) return;
|
|
52
|
+
|
|
53
|
+
let changed = false;
|
|
54
|
+
const content = event.content.map((chunk) => {
|
|
55
|
+
if (chunk.type !== "text" || typeof chunk.text !== "string") return chunk;
|
|
56
|
+
const text = chunk.text;
|
|
57
|
+
if (text.length < MIN_LENGTH || text.includes(MARKER)) return chunk;
|
|
58
|
+
|
|
59
|
+
const reduced = reduceViaCli(text);
|
|
60
|
+
if (!reduced || reduced.length >= text.length) return chunk;
|
|
61
|
+
|
|
62
|
+
if (MODE === "dryrun") {
|
|
63
|
+
runtime.process?.stderr?.write(
|
|
64
|
+
`[harnesstrim] dryrun tool_result: ${text.length} -> ${reduced.length} chars\n`
|
|
65
|
+
);
|
|
66
|
+
return chunk;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
changed = true;
|
|
70
|
+
return { ...chunk, text: reduced };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return changed ? { content } : undefined;
|
|
61
74
|
});
|
|
62
75
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -581,6 +581,75 @@ ${input.slice(response.index)}`;
|
|
|
581
581
|
}
|
|
582
582
|
});
|
|
583
583
|
|
|
584
|
+
// ../core/src/reducers/lint-output-slim.ts
|
|
585
|
+
function isLintLine(line) {
|
|
586
|
+
return LINT_LINE_RE.test(line);
|
|
587
|
+
}
|
|
588
|
+
var MARKER_PREFIX7, LINT_LINE_RE, MAX_RULES_IN_MARKER, lintOutputSlim;
|
|
589
|
+
var init_lint_output_slim = __esm({
|
|
590
|
+
"../core/src/reducers/lint-output-slim.ts"() {
|
|
591
|
+
"use strict";
|
|
592
|
+
MARKER_PREFIX7 = "[harnesstrim:lint-output-slim]";
|
|
593
|
+
LINT_LINE_RE = /^[\w.\/\\-]+:\d+:\d+\s+(warning|error)\s+([\w@.\/-]+)/;
|
|
594
|
+
MAX_RULES_IN_MARKER = 8;
|
|
595
|
+
lintOutputSlim = {
|
|
596
|
+
name: "lint-output-slim",
|
|
597
|
+
reduce(input) {
|
|
598
|
+
const lines = input.split(/\r?\n/);
|
|
599
|
+
const out = [];
|
|
600
|
+
let droppedTotal = 0;
|
|
601
|
+
let i = 0;
|
|
602
|
+
while (i < lines.length) {
|
|
603
|
+
const line = lines[i];
|
|
604
|
+
if (!isLintLine(line)) {
|
|
605
|
+
out.push(line);
|
|
606
|
+
i++;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const counts = [];
|
|
610
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
611
|
+
let runEnd = i;
|
|
612
|
+
while (runEnd < lines.length && isLintLine(lines[runEnd])) {
|
|
613
|
+
const m = LINT_LINE_RE.exec(lines[runEnd]);
|
|
614
|
+
const severity = m[1] === "error" ? "error" : "warning";
|
|
615
|
+
const rule = m[2];
|
|
616
|
+
const key = `${severity}:${rule}`;
|
|
617
|
+
const existing = byKey.get(key);
|
|
618
|
+
if (existing) {
|
|
619
|
+
existing.count++;
|
|
620
|
+
} else {
|
|
621
|
+
const entry = { severity, rule, count: 1 };
|
|
622
|
+
byKey.set(key, entry);
|
|
623
|
+
counts.push(entry);
|
|
624
|
+
}
|
|
625
|
+
runEnd++;
|
|
626
|
+
}
|
|
627
|
+
const runLength = runEnd - i;
|
|
628
|
+
if (runLength >= 2) {
|
|
629
|
+
const parts = counts.slice(0, MAX_RULES_IN_MARKER).map(
|
|
630
|
+
(c) => `${c.rule} \xD7${c.count}`
|
|
631
|
+
);
|
|
632
|
+
const truncated = counts.length > MAX_RULES_IN_MARKER;
|
|
633
|
+
const suffix = truncated ? `, +${counts.length - MAX_RULES_IN_MARKER} more rule(s)` : "";
|
|
634
|
+
const severities = counts.some((c) => c.severity === "error") && counts.some((c) => c.severity === "warning") ? "error(s) and warning(s)" : counts.some((c) => c.severity === "error") ? "error(s)" : "warning(s)";
|
|
635
|
+
out.push(`${MARKER_PREFIX7} omitted ${runLength} lint line(s) (${severities}: ${parts.join(", ")}${suffix})`);
|
|
636
|
+
droppedTotal += runLength;
|
|
637
|
+
} else {
|
|
638
|
+
for (let j = i; j < runEnd; j++) out.push(lines[j]);
|
|
639
|
+
}
|
|
640
|
+
i = runEnd;
|
|
641
|
+
}
|
|
642
|
+
const output = out.join("\n");
|
|
643
|
+
return {
|
|
644
|
+
output,
|
|
645
|
+
changed: droppedTotal > 0,
|
|
646
|
+
note: droppedTotal > 0 ? `dropped ${droppedTotal} lint noise line(s)` : void 0
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
584
653
|
// ../core/src/reducers/index.ts
|
|
585
654
|
var init_reducers = __esm({
|
|
586
655
|
"../core/src/reducers/index.ts"() {
|
|
@@ -592,6 +661,7 @@ var init_reducers = __esm({
|
|
|
592
661
|
init_json_output_slim();
|
|
593
662
|
init_file_listing_slim();
|
|
594
663
|
init_cron_output_slim();
|
|
664
|
+
init_lint_output_slim();
|
|
595
665
|
}
|
|
596
666
|
});
|
|
597
667
|
|
|
@@ -600,6 +670,7 @@ function pickReducer(text) {
|
|
|
600
670
|
if (GIT_DIFF_RE.test(text)) return gitDiffSlim;
|
|
601
671
|
if (TEST_OUTPUT_RE.test(text)) return testOutputSlim;
|
|
602
672
|
if (CRON_OUTPUT_RE.test(text) && text.length >= 400) return cronOutputSlim;
|
|
673
|
+
if (LINT_OUTPUT_RE.test(text) && text.length >= 400) return lintOutputSlim;
|
|
603
674
|
if (JSON_RE.test(text) && text.length >= 400) return jsonOutputSlim;
|
|
604
675
|
if (FILE_LISTING_RE.test(text) && text.length >= 400) return fileListingSlim;
|
|
605
676
|
if (LONG_TEXT_RE.test(text) && text.length >= 1e3) return genericTextSlim;
|
|
@@ -619,7 +690,7 @@ function reduceAuto(text, minLength = DEFAULT_MIN_LENGTH) {
|
|
|
619
690
|
}
|
|
620
691
|
return { ...result, reducer: reducer.name };
|
|
621
692
|
}
|
|
622
|
-
var DEFAULT_MIN_LENGTH, GIT_DIFF_RE, TEST_OUTPUT_RE, JSON_RE, FILE_LISTING_RE, CRON_OUTPUT_RE, LONG_TEXT_RE;
|
|
693
|
+
var DEFAULT_MIN_LENGTH, GIT_DIFF_RE, TEST_OUTPUT_RE, JSON_RE, FILE_LISTING_RE, CRON_OUTPUT_RE, LINT_OUTPUT_RE, LONG_TEXT_RE;
|
|
623
694
|
var init_dispatch = __esm({
|
|
624
695
|
"../core/src/dispatch.ts"() {
|
|
625
696
|
"use strict";
|
|
@@ -629,12 +700,14 @@ var init_dispatch = __esm({
|
|
|
629
700
|
init_json_output_slim();
|
|
630
701
|
init_file_listing_slim();
|
|
631
702
|
init_cron_output_slim();
|
|
703
|
+
init_lint_output_slim();
|
|
632
704
|
DEFAULT_MIN_LENGTH = 400;
|
|
633
705
|
GIT_DIFF_RE = /^diff --git /m;
|
|
634
706
|
TEST_OUTPUT_RE = /\b\d+\s+(passed|failed)\b|^(PASS|FAIL)\s|::\w.*\b(PASSED|FAILED)\b|=+\s*(FAILURES|short test summary)/im;
|
|
635
707
|
JSON_RE = /^\s*[\[{]/m;
|
|
636
708
|
FILE_LISTING_RE = /(?:^total\s+\d+|^[\-bcdlsp][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-]|^\.\/(?:\.|[^.\s])|^\s*(?:├──|└──|│\s+)|^[\w.\/\-]+\.[a-zA-Z]{1,4}:\d+\|)/m;
|
|
637
709
|
CRON_OUTPUT_RE = /^# Cron Job:.*\n[\s\S]*^## Prompt\s*$[\s\S]*^## Response\s*$/m;
|
|
710
|
+
LINT_OUTPUT_RE = /^[\w.\/\\-]+:\d+:\d+\s+(?:warning|error)\s+[\w@.\/-]+\s/m;
|
|
638
711
|
LONG_TEXT_RE = /^#{1,4}\s.*\n(?:(?!^#{1,4}\s|^diff --git |^```).*\n){5,}/m;
|
|
639
712
|
}
|
|
640
713
|
});
|
|
@@ -1166,8 +1239,8 @@ var init_parseUtil = __esm({
|
|
|
1166
1239
|
init_errors();
|
|
1167
1240
|
init_en();
|
|
1168
1241
|
makeIssue = (params) => {
|
|
1169
|
-
const { data, path:
|
|
1170
|
-
const fullPath = [...
|
|
1242
|
+
const { data, path: path16, errorMaps, issueData } = params;
|
|
1243
|
+
const fullPath = [...path16, ...issueData.path || []];
|
|
1171
1244
|
const fullIssue = {
|
|
1172
1245
|
...issueData,
|
|
1173
1246
|
path: fullPath
|
|
@@ -1475,11 +1548,11 @@ var init_types2 = __esm({
|
|
|
1475
1548
|
init_parseUtil();
|
|
1476
1549
|
init_util();
|
|
1477
1550
|
ParseInputLazyPath = class {
|
|
1478
|
-
constructor(parent, value,
|
|
1551
|
+
constructor(parent, value, path16, key) {
|
|
1479
1552
|
this._cachedPath = [];
|
|
1480
1553
|
this.parent = parent;
|
|
1481
1554
|
this.data = value;
|
|
1482
|
-
this._path =
|
|
1555
|
+
this._path = path16;
|
|
1483
1556
|
this._key = key;
|
|
1484
1557
|
}
|
|
1485
1558
|
get path() {
|
|
@@ -5060,10 +5133,10 @@ function assignProp(target, prop, value) {
|
|
|
5060
5133
|
configurable: true
|
|
5061
5134
|
});
|
|
5062
5135
|
}
|
|
5063
|
-
function getElementAtPath(obj,
|
|
5064
|
-
if (!
|
|
5136
|
+
function getElementAtPath(obj, path16) {
|
|
5137
|
+
if (!path16)
|
|
5065
5138
|
return obj;
|
|
5066
|
-
return
|
|
5139
|
+
return path16.reduce((acc, key) => acc?.[key], obj);
|
|
5067
5140
|
}
|
|
5068
5141
|
function promiseAllObject(promisesObj) {
|
|
5069
5142
|
const keys = Object.keys(promisesObj);
|
|
@@ -5312,11 +5385,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5312
5385
|
}
|
|
5313
5386
|
return false;
|
|
5314
5387
|
}
|
|
5315
|
-
function prefixIssues(
|
|
5388
|
+
function prefixIssues(path16, issues) {
|
|
5316
5389
|
return issues.map((iss) => {
|
|
5317
5390
|
var _a;
|
|
5318
5391
|
(_a = iss).path ?? (_a.path = []);
|
|
5319
|
-
iss.path.unshift(
|
|
5392
|
+
iss.path.unshift(path16);
|
|
5320
5393
|
return iss;
|
|
5321
5394
|
});
|
|
5322
5395
|
}
|
|
@@ -17146,8 +17219,8 @@ var require_utils = __commonJS({
|
|
|
17146
17219
|
}
|
|
17147
17220
|
return ind;
|
|
17148
17221
|
}
|
|
17149
|
-
function removeDotSegments(
|
|
17150
|
-
let input =
|
|
17222
|
+
function removeDotSegments(path16) {
|
|
17223
|
+
let input = path16;
|
|
17151
17224
|
const output = [];
|
|
17152
17225
|
let nextSlash = -1;
|
|
17153
17226
|
let len = 0;
|
|
@@ -17399,8 +17472,8 @@ var require_schemes = __commonJS({
|
|
|
17399
17472
|
wsComponent.secure = void 0;
|
|
17400
17473
|
}
|
|
17401
17474
|
if (wsComponent.resourceName) {
|
|
17402
|
-
const [
|
|
17403
|
-
wsComponent.path =
|
|
17475
|
+
const [path16, query] = wsComponent.resourceName.split("?");
|
|
17476
|
+
wsComponent.path = path16 && path16 !== "/" ? path16 : void 0;
|
|
17404
17477
|
wsComponent.query = query;
|
|
17405
17478
|
wsComponent.resourceName = void 0;
|
|
17406
17479
|
}
|
|
@@ -20793,12 +20866,12 @@ var require_dist = __commonJS({
|
|
|
20793
20866
|
throw new Error(`Unknown format "${name}"`);
|
|
20794
20867
|
return f;
|
|
20795
20868
|
};
|
|
20796
|
-
function addFormats(ajv, list,
|
|
20869
|
+
function addFormats(ajv, list, fs12, exportName) {
|
|
20797
20870
|
var _a;
|
|
20798
20871
|
var _b;
|
|
20799
20872
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20800
20873
|
for (const f of list)
|
|
20801
|
-
ajv.addFormat(f,
|
|
20874
|
+
ajv.addFormat(f, fs12[f]);
|
|
20802
20875
|
}
|
|
20803
20876
|
module.exports = exports = formatsPlugin;
|
|
20804
20877
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -22554,15 +22627,39 @@ var init_stdio2 = __esm({
|
|
|
22554
22627
|
// ../mcp/src/server.ts
|
|
22555
22628
|
var server_exports = {};
|
|
22556
22629
|
__export(server_exports, {
|
|
22630
|
+
createFileSink: () => createFileSink,
|
|
22557
22631
|
createServer: () => createServer,
|
|
22558
22632
|
runReduceTool: () => runReduceTool,
|
|
22559
22633
|
startStdioServer: () => startStdioServer
|
|
22560
22634
|
});
|
|
22561
|
-
|
|
22635
|
+
import fs10 from "node:fs";
|
|
22636
|
+
import path14 from "node:path";
|
|
22637
|
+
function createFileSink(metricsPath) {
|
|
22638
|
+
return (event) => {
|
|
22639
|
+
try {
|
|
22640
|
+
const p = path14.resolve(metricsPath);
|
|
22641
|
+
fs10.mkdirSync(path14.dirname(p), { recursive: true });
|
|
22642
|
+
fs10.appendFileSync(p, JSON.stringify(event) + "\n");
|
|
22643
|
+
} catch {
|
|
22644
|
+
}
|
|
22645
|
+
};
|
|
22646
|
+
}
|
|
22647
|
+
function runReduceTool(text, minLength, sink = noopSink) {
|
|
22562
22648
|
const result = reduceAuto(text, minLength);
|
|
22649
|
+
if (result.changed) {
|
|
22650
|
+
sink({
|
|
22651
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22652
|
+
harness: "mcp",
|
|
22653
|
+
tool: "reduce",
|
|
22654
|
+
reducer: result.reducer,
|
|
22655
|
+
beforeChars: text.length,
|
|
22656
|
+
afterChars: result.output.length
|
|
22657
|
+
});
|
|
22658
|
+
}
|
|
22563
22659
|
return { content: [{ type: "text", text: result.output }] };
|
|
22564
22660
|
}
|
|
22565
|
-
function createServer() {
|
|
22661
|
+
function createServer(options = {}) {
|
|
22662
|
+
const sink = options.metricsPath ? createFileSink(options.metricsPath) : noopSink;
|
|
22566
22663
|
const server = new McpServer({ name: "harnesstrim", version: "0.0.1" });
|
|
22567
22664
|
server.registerTool(
|
|
22568
22665
|
"reduce",
|
|
@@ -22574,16 +22671,17 @@ function createServer() {
|
|
|
22574
22671
|
minLength: external_exports.number().optional().describe("Skip reduction for inputs shorter than this many characters (default 400)")
|
|
22575
22672
|
}
|
|
22576
22673
|
},
|
|
22577
|
-
async ({ text, minLength }) => runReduceTool(text, minLength)
|
|
22674
|
+
async ({ text, minLength }) => runReduceTool(text, minLength, sink)
|
|
22578
22675
|
);
|
|
22579
22676
|
return server;
|
|
22580
22677
|
}
|
|
22581
|
-
async function startStdioServer() {
|
|
22582
|
-
const
|
|
22678
|
+
async function startStdioServer(options = {}) {
|
|
22679
|
+
const metricsPath = options.metricsPath ?? process.env.HARNESSTRIM_TELEMETRY_PATH;
|
|
22680
|
+
const server = createServer({ metricsPath });
|
|
22583
22681
|
const transport = new StdioServerTransport();
|
|
22584
22682
|
await server.connect(transport);
|
|
22585
22683
|
}
|
|
22586
|
-
var REDUCE_DESCRIPTION;
|
|
22684
|
+
var noopSink, REDUCE_DESCRIPTION;
|
|
22587
22685
|
var init_server3 = __esm({
|
|
22588
22686
|
"../mcp/src/server.ts"() {
|
|
22589
22687
|
"use strict";
|
|
@@ -22591,6 +22689,8 @@ var init_server3 = __esm({
|
|
|
22591
22689
|
init_stdio2();
|
|
22592
22690
|
init_zod();
|
|
22593
22691
|
init_src();
|
|
22692
|
+
noopSink = () => {
|
|
22693
|
+
};
|
|
22594
22694
|
REDUCE_DESCRIPTION = "Slim noisy text to its signal: keeps failures, errors, assertions and summaries while dropping passing-test noise and generated-file (lockfile/dist) diffs. Pass test-runner output or a git diff and use the returned text instead of the raw output. Deterministic and idempotent; returns the input unchanged if no reducer matches or it is too short.";
|
|
22595
22695
|
}
|
|
22596
22696
|
});
|
|
@@ -22598,9 +22698,9 @@ var init_server3 = __esm({
|
|
|
22598
22698
|
// src/cli.ts
|
|
22599
22699
|
init_src();
|
|
22600
22700
|
import { parseArgs } from "node:util";
|
|
22601
|
-
import
|
|
22602
|
-
import
|
|
22603
|
-
import
|
|
22701
|
+
import fs11 from "node:fs";
|
|
22702
|
+
import path15 from "node:path";
|
|
22703
|
+
import os4 from "node:os";
|
|
22604
22704
|
|
|
22605
22705
|
// src/doctor.ts
|
|
22606
22706
|
import fs from "node:fs";
|
|
@@ -22870,7 +22970,9 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22870
22970
|
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
22871
22971
|
const res = spawnSync(npm, ["install", "--silent"], {
|
|
22872
22972
|
cwd: path2.dirname(packageJsonPath),
|
|
22873
|
-
encoding: "utf8"
|
|
22973
|
+
encoding: "utf8",
|
|
22974
|
+
input: "",
|
|
22975
|
+
timeout: 12e4
|
|
22874
22976
|
});
|
|
22875
22977
|
if (res.error || res.status !== 0) {
|
|
22876
22978
|
depsInstalled = false;
|
|
@@ -22895,6 +22997,7 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22895
22997
|
|
|
22896
22998
|
// src/install-codex.ts
|
|
22897
22999
|
import fs5 from "node:fs";
|
|
23000
|
+
import os from "node:os";
|
|
22898
23001
|
import path6 from "node:path";
|
|
22899
23002
|
|
|
22900
23003
|
// ../adapter-codex/src/index.ts
|
|
@@ -22966,9 +23069,31 @@ function hasHarnessTrimHook(document) {
|
|
|
22966
23069
|
const post = hooks?.PostToolUse;
|
|
22967
23070
|
if (!Array.isArray(post)) return false;
|
|
22968
23071
|
return post.some(
|
|
22969
|
-
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
|
|
23072
|
+
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
|
|
23073
|
+
(hook) => typeof hook?.command === "string" && hook.command.includes("harnesstrim") && hook.command.includes("hook codex")
|
|
23074
|
+
)
|
|
22970
23075
|
);
|
|
22971
23076
|
}
|
|
23077
|
+
function hasExactHarnessTrimHookCommand(document, command) {
|
|
23078
|
+
const hooks = document.hooks;
|
|
23079
|
+
const post = hooks?.PostToolUse;
|
|
23080
|
+
return Array.isArray(post) && post.some(
|
|
23081
|
+
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some((hook) => hook?.command === command)
|
|
23082
|
+
);
|
|
23083
|
+
}
|
|
23084
|
+
function replaceHarnessTrimHookCommand(document, command) {
|
|
23085
|
+
const hooks = document.hooks;
|
|
23086
|
+
const post = hooks?.PostToolUse;
|
|
23087
|
+
if (!Array.isArray(post)) return;
|
|
23088
|
+
for (const entry of post) {
|
|
23089
|
+
if (!Array.isArray(entry?.hooks)) continue;
|
|
23090
|
+
for (const hook of entry.hooks) {
|
|
23091
|
+
if (typeof hook?.command === "string" && hook.command.includes("harnesstrim") && hook.command.includes("hook codex")) {
|
|
23092
|
+
hook.command = command;
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
23095
|
+
}
|
|
23096
|
+
}
|
|
22972
23097
|
function planCodexHookInstall(input) {
|
|
22973
23098
|
let document = {};
|
|
22974
23099
|
let action;
|
|
@@ -22985,14 +23110,21 @@ function planCodexHookInstall(input) {
|
|
|
22985
23110
|
throw new Error(".codex/hooks.json must contain a JSON object; refusing to overwrite it.");
|
|
22986
23111
|
}
|
|
22987
23112
|
document = parsed;
|
|
22988
|
-
action = hasHarnessTrimHook(document) ? "present" : "patch";
|
|
23113
|
+
action = hasHarnessTrimHook(document) && (!input.hookCommand || hasExactHarnessTrimHookCommand(document, input.hookCommand)) ? "present" : "patch";
|
|
22989
23114
|
}
|
|
22990
23115
|
if (action === "present") {
|
|
22991
23116
|
return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
|
|
22992
23117
|
}
|
|
23118
|
+
if (hasHarnessTrimHook(document)) {
|
|
23119
|
+
replaceHarnessTrimHookCommand(document, input.hookCommand ?? CODEX_HOOK_COMMAND);
|
|
23120
|
+
return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
|
|
23121
|
+
}
|
|
22993
23122
|
const hooks = { ...document.hooks ?? {} };
|
|
22994
23123
|
const post = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
|
|
22995
|
-
post.push({
|
|
23124
|
+
post.push({
|
|
23125
|
+
matcher: CODEX_HOOK_MATCHER,
|
|
23126
|
+
hooks: [{ type: "command", command: input.hookCommand ?? CODEX_HOOK_COMMAND }]
|
|
23127
|
+
});
|
|
22996
23128
|
hooks.PostToolUse = post;
|
|
22997
23129
|
return {
|
|
22998
23130
|
hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"),
|
|
@@ -23067,6 +23199,13 @@ function existingSkillNames(dest) {
|
|
|
23067
23199
|
}
|
|
23068
23200
|
|
|
23069
23201
|
// src/install-codex.ts
|
|
23202
|
+
function resolveCodexHookCommand() {
|
|
23203
|
+
if (process.platform !== "win32") return void 0;
|
|
23204
|
+
const pnpmHome = process.env.PNPM_HOME ?? path6.join(process.env.LOCALAPPDATA ?? path6.join(os.homedir(), "AppData", "Local"), "pnpm");
|
|
23205
|
+
const shim = path6.join(pnpmHome, "harnesstrim.CMD");
|
|
23206
|
+
if (!fs5.existsSync(shim)) return void 0;
|
|
23207
|
+
return `"${shim}" hook codex --metrics .harnesstrim/metrics.jsonl`;
|
|
23208
|
+
}
|
|
23070
23209
|
function readHooksJson(hooksPath) {
|
|
23071
23210
|
try {
|
|
23072
23211
|
return fs5.readFileSync(hooksPath, "utf8");
|
|
@@ -23100,7 +23239,7 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23100
23239
|
});
|
|
23101
23240
|
const hooksPath = path6.join(dir, ".codex", "hooks.json");
|
|
23102
23241
|
const hooksJsonContent = hook ? readHooksJson(hooksPath) : null;
|
|
23103
|
-
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent }) : null;
|
|
23242
|
+
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent, hookCommand: resolveCodexHookCommand() }) : null;
|
|
23104
23243
|
const copied = [];
|
|
23105
23244
|
let applied = false;
|
|
23106
23245
|
if (apply) {
|
|
@@ -23125,7 +23264,8 @@ function runInstallCodexGlobalHook(codexHome, apply) {
|
|
|
23125
23264
|
// The planner expects the directory that contains .codex; for a user-level config
|
|
23126
23265
|
// the Codex home is itself that directory, so add its parent and use a normal path.
|
|
23127
23266
|
projectDir: path6.dirname(codexHome),
|
|
23128
|
-
hooksJsonContent: readHooksJson(hooksPath)
|
|
23267
|
+
hooksJsonContent: readHooksJson(hooksPath),
|
|
23268
|
+
hookCommand: resolveCodexHookCommand()
|
|
23129
23269
|
});
|
|
23130
23270
|
return { hookPlan, applied: applyHookPlan(hookPlan, apply) };
|
|
23131
23271
|
}
|
|
@@ -23187,6 +23327,19 @@ function extractOutputText2(p) {
|
|
|
23187
23327
|
import path7 from "node:path";
|
|
23188
23328
|
var HOOK_COMMAND = "harnesstrim hook claude";
|
|
23189
23329
|
var HOOK_MATCHER = "Bash";
|
|
23330
|
+
var HARNESSTRIM_MARKER2 = "harnesstrim:begin";
|
|
23331
|
+
var REDUCE_INSTRUCTION_SNIPPET2 = `<!-- ${HARNESSTRIM_MARKER2} -->
|
|
23332
|
+
## Token economy (HarnessTrim)
|
|
23333
|
+
|
|
23334
|
+
When a shell command produces long, noisy output \u2014 test runners, \`git diff\`, build logs,
|
|
23335
|
+
large file dumps \u2014 pipe it through the reducer so only the signal enters context:
|
|
23336
|
+
|
|
23337
|
+
<your command> 2>&1 | harnesstrim reduce --metrics .harnesstrim/metrics.jsonl
|
|
23338
|
+
|
|
23339
|
+
This keeps failures, errors, assertions, and summaries while dropping passing-test noise and
|
|
23340
|
+
generated-file (lockfile/dist) diffs, and records what was saved. Prefer the installed skills
|
|
23341
|
+
for output, review, and scaffolding discipline.
|
|
23342
|
+
<!-- harnesstrim:end -->`;
|
|
23190
23343
|
function hasHarnessTrimHook2(settings) {
|
|
23191
23344
|
const hooks = settings.hooks;
|
|
23192
23345
|
const post = hooks?.PostToolUse;
|
|
@@ -23218,12 +23371,16 @@ function planClaudeInstall(input) {
|
|
|
23218
23371
|
action = hasHarnessTrimHook2(settings) ? "present" : "patch";
|
|
23219
23372
|
}
|
|
23220
23373
|
const nextSettings = action === "present" ? settings : addHook(settings);
|
|
23374
|
+
const instructionsAction = input.claudeMdContent === null ? "create" : input.claudeMdContent.includes(HARNESSTRIM_MARKER2) ? "present" : "append";
|
|
23221
23375
|
return {
|
|
23222
23376
|
skillsDest,
|
|
23223
23377
|
skills,
|
|
23224
23378
|
settingsFile: path7.join(input.projectDir, ".claude", "settings.json"),
|
|
23225
23379
|
settingsAction: action,
|
|
23226
|
-
nextSettings
|
|
23380
|
+
nextSettings,
|
|
23381
|
+
instructionsFile: path7.join(input.projectDir, "CLAUDE.md"),
|
|
23382
|
+
instructionsAction,
|
|
23383
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2
|
|
23227
23384
|
};
|
|
23228
23385
|
}
|
|
23229
23386
|
function addHook(settings) {
|
|
@@ -23248,11 +23405,19 @@ function runInstallClaude(dir, apply) {
|
|
|
23248
23405
|
} catch {
|
|
23249
23406
|
settingsJsonContent = null;
|
|
23250
23407
|
}
|
|
23408
|
+
const claudeMdPath = path8.join(dir, "CLAUDE.md");
|
|
23409
|
+
let claudeMdContent = null;
|
|
23410
|
+
try {
|
|
23411
|
+
claudeMdContent = fs6.readFileSync(claudeMdPath, "utf8");
|
|
23412
|
+
} catch {
|
|
23413
|
+
claudeMdContent = null;
|
|
23414
|
+
}
|
|
23251
23415
|
const plan = planClaudeInstall({
|
|
23252
23416
|
projectDir: dir,
|
|
23253
23417
|
skillsSourceDir,
|
|
23254
23418
|
skillNames,
|
|
23255
23419
|
settingsJsonContent,
|
|
23420
|
+
claudeMdContent,
|
|
23256
23421
|
existingSkillNames: existingSkillNames(skillsDest)
|
|
23257
23422
|
});
|
|
23258
23423
|
const copied = [];
|
|
@@ -23267,6 +23432,11 @@ function runInstallClaude(dir, apply) {
|
|
|
23267
23432
|
fs6.mkdirSync(path8.dirname(plan.settingsFile), { recursive: true });
|
|
23268
23433
|
fs6.writeFileSync(plan.settingsFile, JSON.stringify(plan.nextSettings, null, 2) + "\n");
|
|
23269
23434
|
}
|
|
23435
|
+
if (plan.instructionsAction === "create") {
|
|
23436
|
+
fs6.writeFileSync(plan.instructionsFile, plan.instructionsSnippet + "\n");
|
|
23437
|
+
} else if (plan.instructionsAction === "append") {
|
|
23438
|
+
fs6.appendFileSync(plan.instructionsFile, "\n\n" + plan.instructionsSnippet + "\n");
|
|
23439
|
+
}
|
|
23270
23440
|
applied = true;
|
|
23271
23441
|
}
|
|
23272
23442
|
return { plan, applied, copied };
|
|
@@ -23275,12 +23445,13 @@ function runInstallClaude(dir, apply) {
|
|
|
23275
23445
|
// src/install-pi.ts
|
|
23276
23446
|
import fs7 from "node:fs";
|
|
23277
23447
|
import path10 from "node:path";
|
|
23448
|
+
import os2 from "node:os";
|
|
23278
23449
|
|
|
23279
23450
|
// ../adapter-pi/src/index.ts
|
|
23280
23451
|
import path9 from "node:path";
|
|
23281
23452
|
var PI_EXTENSION_NAME = "harnesstrim";
|
|
23282
23453
|
function planPiInstall(input) {
|
|
23283
|
-
const extensionDest = path9.join(input.installDir, ".pi", "extensions", PI_EXTENSION_NAME);
|
|
23454
|
+
const extensionDest = input.scope === "user" ? path9.join(input.installDir, ".pi", "agent", "extensions", PI_EXTENSION_NAME) : path9.join(input.installDir, ".pi", "extensions", PI_EXTENSION_NAME);
|
|
23284
23455
|
return {
|
|
23285
23456
|
extensionDest,
|
|
23286
23457
|
extensionSource: input.extensionSourceDir,
|
|
@@ -23315,16 +23486,18 @@ function markerPresent(dest) {
|
|
|
23315
23486
|
}
|
|
23316
23487
|
function runInstallPi(installDir, apply) {
|
|
23317
23488
|
const extensionSourceDir = resolvePiExtensionSourceDir();
|
|
23318
|
-
const
|
|
23489
|
+
const scope = path10.resolve(installDir) === path10.resolve(os2.homedir()) ? "user" : "project";
|
|
23490
|
+
const dest = scope === "user" ? path10.join(installDir, ".pi", "agent", "extensions", "harnesstrim") : path10.join(installDir, ".pi", "extensions", "harnesstrim");
|
|
23319
23491
|
const plan = planPiInstall({
|
|
23320
23492
|
installDir,
|
|
23321
23493
|
extensionSourceDir,
|
|
23322
23494
|
extensionDirExists: dirExists(dest),
|
|
23323
|
-
markerPresent: markerPresent(dest)
|
|
23495
|
+
markerPresent: markerPresent(dest),
|
|
23496
|
+
scope
|
|
23324
23497
|
});
|
|
23325
23498
|
const copiedFiles = [];
|
|
23326
23499
|
let applied = false;
|
|
23327
|
-
if (apply
|
|
23500
|
+
if (apply) {
|
|
23328
23501
|
fs7.mkdirSync(dest, { recursive: true });
|
|
23329
23502
|
for (const entry of fs7.readdirSync(extensionSourceDir, { withFileTypes: true })) {
|
|
23330
23503
|
if (!entry.isDirectory()) {
|
|
@@ -23428,8 +23601,8 @@ function runInstallHermes(installDir, apply) {
|
|
|
23428
23601
|
init_src();
|
|
23429
23602
|
import fs9 from "node:fs";
|
|
23430
23603
|
import path13 from "node:path";
|
|
23431
|
-
import
|
|
23432
|
-
var HERMES_METRICS_PATH = path13.join(
|
|
23604
|
+
import os3 from "node:os";
|
|
23605
|
+
var HERMES_METRICS_PATH = path13.join(os3.homedir(), ".hermes", "harnesstrim-metrics.jsonl");
|
|
23433
23606
|
var LOCAL_METRICS_PATH = ".harnesstrim/metrics.jsonl";
|
|
23434
23607
|
var DEFAULT_METRICS_PATH = fs9.existsSync(HERMES_METRICS_PATH) ? HERMES_METRICS_PATH : LOCAL_METRICS_PATH;
|
|
23435
23608
|
function loadMetrics(filePath) {
|
|
@@ -23445,9 +23618,46 @@ function loadMetrics(filePath) {
|
|
|
23445
23618
|
return { path: filePath, found: true, summary: summarize(parseTrimEvents(raw)) };
|
|
23446
23619
|
}
|
|
23447
23620
|
|
|
23621
|
+
// package.json
|
|
23622
|
+
var package_default = {
|
|
23623
|
+
name: "harnesstrim",
|
|
23624
|
+
version: "0.0.6",
|
|
23625
|
+
description: "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
23626
|
+
license: "MIT",
|
|
23627
|
+
type: "module",
|
|
23628
|
+
bin: {
|
|
23629
|
+
harnesstrim: "./dist/cli.mjs"
|
|
23630
|
+
},
|
|
23631
|
+
files: [
|
|
23632
|
+
"dist",
|
|
23633
|
+
"assets"
|
|
23634
|
+
],
|
|
23635
|
+
publishConfig: {
|
|
23636
|
+
access: "public"
|
|
23637
|
+
},
|
|
23638
|
+
devDependencies: {
|
|
23639
|
+
"@harnesstrim/adapter-claude": "workspace:*",
|
|
23640
|
+
"@harnesstrim/adapter-codex": "workspace:*",
|
|
23641
|
+
"@harnesstrim/adapter-hermes": "workspace:*",
|
|
23642
|
+
"@harnesstrim/adapter-pi": "workspace:*",
|
|
23643
|
+
"@harnesstrim/benchmarks": "workspace:*",
|
|
23644
|
+
"@harnesstrim/core": "workspace:*",
|
|
23645
|
+
"@harnesstrim/mcp": "workspace:*",
|
|
23646
|
+
esbuild: "^0.25.0"
|
|
23647
|
+
},
|
|
23648
|
+
scripts: {
|
|
23649
|
+
build: "node build.mjs",
|
|
23650
|
+
prepare: "node build.mjs",
|
|
23651
|
+
prepack: "node build.mjs",
|
|
23652
|
+
test: 'node --test "src/**/*.test.ts"',
|
|
23653
|
+
typecheck: "tsc -p tsconfig.json"
|
|
23654
|
+
}
|
|
23655
|
+
};
|
|
23656
|
+
|
|
23448
23657
|
// src/reduce.ts
|
|
23449
23658
|
init_src();
|
|
23450
23659
|
async function readStdin() {
|
|
23660
|
+
if (process.stdin.isTTY) return "";
|
|
23451
23661
|
const chunks = [];
|
|
23452
23662
|
for await (const chunk of process.stdin) {
|
|
23453
23663
|
chunks.push(chunk);
|
|
@@ -23619,7 +23829,19 @@ function renderClaudeInstall(result, apply) {
|
|
|
23619
23829
|
}
|
|
23620
23830
|
}
|
|
23621
23831
|
lines.push("");
|
|
23622
|
-
|
|
23832
|
+
if (plan.instructionsAction === "present") {
|
|
23833
|
+
lines.push(`${plan.instructionsFile}: reduce-pipe instruction already present (no change).`);
|
|
23834
|
+
} else {
|
|
23835
|
+
lines.push(
|
|
23836
|
+
`${plan.instructionsFile}: reduce-pipe instruction ${apply ? plan.instructionsAction === "create" ? "created" : "appended" : "would be added"}.`
|
|
23837
|
+
);
|
|
23838
|
+
}
|
|
23839
|
+
lines.push("");
|
|
23840
|
+
lines.push("Note: `harnesstrim` must be on PATH (used by both the hook and the reduce pipe).");
|
|
23841
|
+
lines.push(
|
|
23842
|
+
"The CLAUDE.md instruction is the effective path today: current Claude Code versions don't apply"
|
|
23843
|
+
);
|
|
23844
|
+
lines.push("the hook's updatedToolOutput, so piping through `harnesstrim reduce` is what saves tokens.");
|
|
23623
23845
|
if (!apply) lines.push("Dry run \u2014 nothing written. Re-run with `--apply`.");
|
|
23624
23846
|
return lines.join("\n");
|
|
23625
23847
|
}
|
|
@@ -23723,10 +23945,13 @@ Usage:
|
|
|
23723
23945
|
harnesstrim preset list List policy presets
|
|
23724
23946
|
harnesstrim preset show <name> Show a preset in detail
|
|
23725
23947
|
harnesstrim metrics [path] Summarize adapter telemetry (JSONL)
|
|
23726
|
-
harnesstrim reduce [--stats]
|
|
23727
|
-
|
|
23948
|
+
harnesstrim reduce [--stats] [--metrics <path>]
|
|
23949
|
+
Slim stdin -> stdout (pipe noisy command output);
|
|
23950
|
+
--metrics records a TrimEvent per reduction
|
|
23951
|
+
harnesstrim mcp [--metrics <path>] Start the MCP server (stdio) exposing a reduce tool;
|
|
23952
|
+
--metrics records a TrimEvent per reduction
|
|
23728
23953
|
harnesstrim bench Run the Tier A reducer micro-benchmark
|
|
23729
|
-
harnesstrim --
|
|
23954
|
+
harnesstrim --version Print the installed version
|
|
23730
23955
|
|
|
23731
23956
|
Notes:
|
|
23732
23957
|
- install is dry-run by default; nothing is written without --apply.
|
|
@@ -23738,6 +23963,7 @@ async function main(argv) {
|
|
|
23738
23963
|
allowPositionals: true,
|
|
23739
23964
|
options: {
|
|
23740
23965
|
help: { type: "boolean", short: "h" },
|
|
23966
|
+
version: { type: "boolean", short: "v" },
|
|
23741
23967
|
apply: { type: "boolean" },
|
|
23742
23968
|
preset: { type: "string" },
|
|
23743
23969
|
stats: { type: "boolean" },
|
|
@@ -23749,6 +23975,10 @@ async function main(argv) {
|
|
|
23749
23975
|
}
|
|
23750
23976
|
});
|
|
23751
23977
|
const [command, ...rest] = positionals;
|
|
23978
|
+
if (values.version) {
|
|
23979
|
+
console.log(package_default.version);
|
|
23980
|
+
return 0;
|
|
23981
|
+
}
|
|
23752
23982
|
if (values.help || !command) {
|
|
23753
23983
|
console.log(HELP);
|
|
23754
23984
|
return 0;
|
|
@@ -23761,7 +23991,7 @@ async function main(argv) {
|
|
|
23761
23991
|
}
|
|
23762
23992
|
case "install": {
|
|
23763
23993
|
const target = rest[0];
|
|
23764
|
-
const dir = rest[1] ?? (target === "hermes" ?
|
|
23994
|
+
const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
|
|
23765
23995
|
const apply = values.apply === true;
|
|
23766
23996
|
if (target === "opencode") {
|
|
23767
23997
|
const result = runInstallOpencode(dir, apply, values.preset);
|
|
@@ -23774,7 +24004,7 @@ async function main(argv) {
|
|
|
23774
24004
|
console.error("`harnesstrim install codex --global` requires `--hook`.");
|
|
23775
24005
|
return 1;
|
|
23776
24006
|
}
|
|
23777
|
-
console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(
|
|
24007
|
+
console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path15.join(os4.homedir(), ".codex"), apply), apply));
|
|
23778
24008
|
return 0;
|
|
23779
24009
|
}
|
|
23780
24010
|
console.log(renderCodexInstall(runInstallCodex(dir, apply, values.hook === true), apply));
|
|
@@ -23806,9 +24036,9 @@ async function main(argv) {
|
|
|
23806
24036
|
process.stdout.write(response);
|
|
23807
24037
|
if (values.metrics && event) {
|
|
23808
24038
|
try {
|
|
23809
|
-
const p =
|
|
23810
|
-
|
|
23811
|
-
|
|
24039
|
+
const p = path15.resolve(values.metrics);
|
|
24040
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
24041
|
+
fs11.appendFileSync(
|
|
23812
24042
|
p,
|
|
23813
24043
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), harness: which, ...event }) + "\n"
|
|
23814
24044
|
);
|
|
@@ -23817,7 +24047,7 @@ async function main(argv) {
|
|
|
23817
24047
|
}
|
|
23818
24048
|
if (values.log) {
|
|
23819
24049
|
try {
|
|
23820
|
-
|
|
24050
|
+
fs11.appendFileSync(
|
|
23821
24051
|
values.log,
|
|
23822
24052
|
JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
|
|
23823
24053
|
);
|
|
@@ -23846,8 +24076,8 @@ async function main(argv) {
|
|
|
23846
24076
|
return 1;
|
|
23847
24077
|
}
|
|
23848
24078
|
case "metrics": {
|
|
23849
|
-
const
|
|
23850
|
-
console.log(renderMetrics(loadMetrics(
|
|
24079
|
+
const path16 = rest[0] ?? DEFAULT_METRICS_PATH;
|
|
24080
|
+
console.log(renderMetrics(loadMetrics(path16)));
|
|
23851
24081
|
return 0;
|
|
23852
24082
|
}
|
|
23853
24083
|
case "reduce": {
|
|
@@ -23860,6 +24090,24 @@ async function main(argv) {
|
|
|
23860
24090
|
const input = await readStdin();
|
|
23861
24091
|
const result = reducePipe(input, minLength);
|
|
23862
24092
|
process.stdout.write(result.output);
|
|
24093
|
+
if (values.metrics && result.changed) {
|
|
24094
|
+
try {
|
|
24095
|
+
const p = path15.resolve(values.metrics);
|
|
24096
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
24097
|
+
fs11.appendFileSync(
|
|
24098
|
+
p,
|
|
24099
|
+
JSON.stringify({
|
|
24100
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24101
|
+
harness: "pipe",
|
|
24102
|
+
tool: "reduce",
|
|
24103
|
+
reducer: result.reducer,
|
|
24104
|
+
beforeChars: result.beforeChars,
|
|
24105
|
+
afterChars: result.afterChars
|
|
24106
|
+
}) + "\n"
|
|
24107
|
+
);
|
|
24108
|
+
} catch {
|
|
24109
|
+
}
|
|
24110
|
+
}
|
|
23863
24111
|
if (values.stats) {
|
|
23864
24112
|
const note = result.changed ? `${result.reducer}: ${result.beforeChars} -> ${result.afterChars} chars` : "no reduction (no reducer matched or below min-length)";
|
|
23865
24113
|
console.error(`[harnesstrim reduce] ${note}`);
|
|
@@ -23868,7 +24116,7 @@ async function main(argv) {
|
|
|
23868
24116
|
}
|
|
23869
24117
|
case "mcp": {
|
|
23870
24118
|
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
|
|
23871
|
-
await startStdioServer2();
|
|
24119
|
+
await startStdioServer2(values.metrics ? { metricsPath: values.metrics } : {});
|
|
23872
24120
|
await new Promise(() => {
|
|
23873
24121
|
});
|
|
23874
24122
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harnesstrim",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,20 +15,18 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"
|
|
19
|
-
"@harnesstrim/adapter-
|
|
20
|
-
"@harnesstrim/adapter-hermes": "
|
|
21
|
-
"@harnesstrim/
|
|
22
|
-
"@harnesstrim/
|
|
23
|
-
"@harnesstrim/
|
|
24
|
-
"@harnesstrim/mcp": "
|
|
25
|
-
"
|
|
18
|
+
"esbuild": "^0.25.0",
|
|
19
|
+
"@harnesstrim/adapter-claude": "0.0.1",
|
|
20
|
+
"@harnesstrim/adapter-hermes": "0.0.1",
|
|
21
|
+
"@harnesstrim/core": "0.0.2",
|
|
22
|
+
"@harnesstrim/adapter-codex": "0.0.1",
|
|
23
|
+
"@harnesstrim/benchmarks": "0.0.1",
|
|
24
|
+
"@harnesstrim/mcp": "0.0.1",
|
|
25
|
+
"@harnesstrim/adapter-pi": "0.0.1"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "node build.mjs",
|
|
29
|
-
"prepare": "node build.mjs",
|
|
30
|
-
"prepack": "node build.mjs",
|
|
31
29
|
"test": "node --test \"src/**/*.test.ts\"",
|
|
32
30
|
"typecheck": "tsc -p tsconfig.json"
|
|
33
31
|
}
|
|
34
|
-
}
|
|
32
|
+
}
|