harnesstrim 0.0.3 → 0.0.5
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/dist/cli.mjs +310 -115
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1166,8 +1166,8 @@ var init_parseUtil = __esm({
|
|
|
1166
1166
|
init_errors();
|
|
1167
1167
|
init_en();
|
|
1168
1168
|
makeIssue = (params) => {
|
|
1169
|
-
const { data, path:
|
|
1170
|
-
const fullPath = [...
|
|
1169
|
+
const { data, path: path16, errorMaps, issueData } = params;
|
|
1170
|
+
const fullPath = [...path16, ...issueData.path || []];
|
|
1171
1171
|
const fullIssue = {
|
|
1172
1172
|
...issueData,
|
|
1173
1173
|
path: fullPath
|
|
@@ -1475,11 +1475,11 @@ var init_types2 = __esm({
|
|
|
1475
1475
|
init_parseUtil();
|
|
1476
1476
|
init_util();
|
|
1477
1477
|
ParseInputLazyPath = class {
|
|
1478
|
-
constructor(parent, value,
|
|
1478
|
+
constructor(parent, value, path16, key) {
|
|
1479
1479
|
this._cachedPath = [];
|
|
1480
1480
|
this.parent = parent;
|
|
1481
1481
|
this.data = value;
|
|
1482
|
-
this._path =
|
|
1482
|
+
this._path = path16;
|
|
1483
1483
|
this._key = key;
|
|
1484
1484
|
}
|
|
1485
1485
|
get path() {
|
|
@@ -5060,10 +5060,10 @@ function assignProp(target, prop, value) {
|
|
|
5060
5060
|
configurable: true
|
|
5061
5061
|
});
|
|
5062
5062
|
}
|
|
5063
|
-
function getElementAtPath(obj,
|
|
5064
|
-
if (!
|
|
5063
|
+
function getElementAtPath(obj, path16) {
|
|
5064
|
+
if (!path16)
|
|
5065
5065
|
return obj;
|
|
5066
|
-
return
|
|
5066
|
+
return path16.reduce((acc, key) => acc?.[key], obj);
|
|
5067
5067
|
}
|
|
5068
5068
|
function promiseAllObject(promisesObj) {
|
|
5069
5069
|
const keys = Object.keys(promisesObj);
|
|
@@ -5312,11 +5312,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5312
5312
|
}
|
|
5313
5313
|
return false;
|
|
5314
5314
|
}
|
|
5315
|
-
function prefixIssues(
|
|
5315
|
+
function prefixIssues(path16, issues) {
|
|
5316
5316
|
return issues.map((iss) => {
|
|
5317
5317
|
var _a;
|
|
5318
5318
|
(_a = iss).path ?? (_a.path = []);
|
|
5319
|
-
iss.path.unshift(
|
|
5319
|
+
iss.path.unshift(path16);
|
|
5320
5320
|
return iss;
|
|
5321
5321
|
});
|
|
5322
5322
|
}
|
|
@@ -17146,8 +17146,8 @@ var require_utils = __commonJS({
|
|
|
17146
17146
|
}
|
|
17147
17147
|
return ind;
|
|
17148
17148
|
}
|
|
17149
|
-
function removeDotSegments(
|
|
17150
|
-
let input =
|
|
17149
|
+
function removeDotSegments(path16) {
|
|
17150
|
+
let input = path16;
|
|
17151
17151
|
const output = [];
|
|
17152
17152
|
let nextSlash = -1;
|
|
17153
17153
|
let len = 0;
|
|
@@ -17399,8 +17399,8 @@ var require_schemes = __commonJS({
|
|
|
17399
17399
|
wsComponent.secure = void 0;
|
|
17400
17400
|
}
|
|
17401
17401
|
if (wsComponent.resourceName) {
|
|
17402
|
-
const [
|
|
17403
|
-
wsComponent.path =
|
|
17402
|
+
const [path16, query] = wsComponent.resourceName.split("?");
|
|
17403
|
+
wsComponent.path = path16 && path16 !== "/" ? path16 : void 0;
|
|
17404
17404
|
wsComponent.query = query;
|
|
17405
17405
|
wsComponent.resourceName = void 0;
|
|
17406
17406
|
}
|
|
@@ -20793,12 +20793,12 @@ var require_dist = __commonJS({
|
|
|
20793
20793
|
throw new Error(`Unknown format "${name}"`);
|
|
20794
20794
|
return f;
|
|
20795
20795
|
};
|
|
20796
|
-
function addFormats(ajv, list,
|
|
20796
|
+
function addFormats(ajv, list, fs12, exportName) {
|
|
20797
20797
|
var _a;
|
|
20798
20798
|
var _b;
|
|
20799
20799
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20800
20800
|
for (const f of list)
|
|
20801
|
-
ajv.addFormat(f,
|
|
20801
|
+
ajv.addFormat(f, fs12[f]);
|
|
20802
20802
|
}
|
|
20803
20803
|
module.exports = exports = formatsPlugin;
|
|
20804
20804
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -22554,15 +22554,39 @@ var init_stdio2 = __esm({
|
|
|
22554
22554
|
// ../mcp/src/server.ts
|
|
22555
22555
|
var server_exports = {};
|
|
22556
22556
|
__export(server_exports, {
|
|
22557
|
+
createFileSink: () => createFileSink,
|
|
22557
22558
|
createServer: () => createServer,
|
|
22558
22559
|
runReduceTool: () => runReduceTool,
|
|
22559
22560
|
startStdioServer: () => startStdioServer
|
|
22560
22561
|
});
|
|
22561
|
-
|
|
22562
|
+
import fs10 from "node:fs";
|
|
22563
|
+
import path14 from "node:path";
|
|
22564
|
+
function createFileSink(metricsPath) {
|
|
22565
|
+
return (event) => {
|
|
22566
|
+
try {
|
|
22567
|
+
const p = path14.resolve(metricsPath);
|
|
22568
|
+
fs10.mkdirSync(path14.dirname(p), { recursive: true });
|
|
22569
|
+
fs10.appendFileSync(p, JSON.stringify(event) + "\n");
|
|
22570
|
+
} catch {
|
|
22571
|
+
}
|
|
22572
|
+
};
|
|
22573
|
+
}
|
|
22574
|
+
function runReduceTool(text, minLength, sink = noopSink) {
|
|
22562
22575
|
const result = reduceAuto(text, minLength);
|
|
22576
|
+
if (result.changed) {
|
|
22577
|
+
sink({
|
|
22578
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22579
|
+
harness: "mcp",
|
|
22580
|
+
tool: "reduce",
|
|
22581
|
+
reducer: result.reducer,
|
|
22582
|
+
beforeChars: text.length,
|
|
22583
|
+
afterChars: result.output.length
|
|
22584
|
+
});
|
|
22585
|
+
}
|
|
22563
22586
|
return { content: [{ type: "text", text: result.output }] };
|
|
22564
22587
|
}
|
|
22565
|
-
function createServer() {
|
|
22588
|
+
function createServer(options = {}) {
|
|
22589
|
+
const sink = options.metricsPath ? createFileSink(options.metricsPath) : noopSink;
|
|
22566
22590
|
const server = new McpServer({ name: "harnesstrim", version: "0.0.1" });
|
|
22567
22591
|
server.registerTool(
|
|
22568
22592
|
"reduce",
|
|
@@ -22574,16 +22598,17 @@ function createServer() {
|
|
|
22574
22598
|
minLength: external_exports.number().optional().describe("Skip reduction for inputs shorter than this many characters (default 400)")
|
|
22575
22599
|
}
|
|
22576
22600
|
},
|
|
22577
|
-
async ({ text, minLength }) => runReduceTool(text, minLength)
|
|
22601
|
+
async ({ text, minLength }) => runReduceTool(text, minLength, sink)
|
|
22578
22602
|
);
|
|
22579
22603
|
return server;
|
|
22580
22604
|
}
|
|
22581
|
-
async function startStdioServer() {
|
|
22582
|
-
const
|
|
22605
|
+
async function startStdioServer(options = {}) {
|
|
22606
|
+
const metricsPath = options.metricsPath ?? process.env.HARNESSTRIM_TELEMETRY_PATH;
|
|
22607
|
+
const server = createServer({ metricsPath });
|
|
22583
22608
|
const transport = new StdioServerTransport();
|
|
22584
22609
|
await server.connect(transport);
|
|
22585
22610
|
}
|
|
22586
|
-
var REDUCE_DESCRIPTION;
|
|
22611
|
+
var noopSink, REDUCE_DESCRIPTION;
|
|
22587
22612
|
var init_server3 = __esm({
|
|
22588
22613
|
"../mcp/src/server.ts"() {
|
|
22589
22614
|
"use strict";
|
|
@@ -22591,6 +22616,8 @@ var init_server3 = __esm({
|
|
|
22591
22616
|
init_stdio2();
|
|
22592
22617
|
init_zod();
|
|
22593
22618
|
init_src();
|
|
22619
|
+
noopSink = () => {
|
|
22620
|
+
};
|
|
22594
22621
|
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
22622
|
}
|
|
22596
22623
|
});
|
|
@@ -22598,8 +22625,8 @@ var init_server3 = __esm({
|
|
|
22598
22625
|
// src/cli.ts
|
|
22599
22626
|
init_src();
|
|
22600
22627
|
import { parseArgs } from "node:util";
|
|
22601
|
-
import
|
|
22602
|
-
import
|
|
22628
|
+
import fs11 from "node:fs";
|
|
22629
|
+
import path15 from "node:path";
|
|
22603
22630
|
import os2 from "node:os";
|
|
22604
22631
|
|
|
22605
22632
|
// src/doctor.ts
|
|
@@ -22681,40 +22708,45 @@ function checkSkills(dir, findings) {
|
|
|
22681
22708
|
}
|
|
22682
22709
|
}
|
|
22683
22710
|
function checkOpenCode(dir, findings) {
|
|
22684
|
-
const
|
|
22685
|
-
if (
|
|
22711
|
+
const hasOpencodeProject = readIfExists(path.join(dir, "opencode.json")) !== null || fs.existsSync(path.join(dir, ".opencode"));
|
|
22712
|
+
if (!hasOpencodeProject) {
|
|
22686
22713
|
findings.push({
|
|
22687
22714
|
severity: "info",
|
|
22688
|
-
title: "No
|
|
22689
|
-
detail: "No
|
|
22715
|
+
title: "No OpenCode project config found",
|
|
22716
|
+
detail: "No opencode.json or .opencode/ in this directory."
|
|
22690
22717
|
});
|
|
22691
22718
|
return;
|
|
22692
22719
|
}
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22697
|
-
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
}
|
|
22702
|
-
return;
|
|
22720
|
+
const wrapper = path.join(dir, ".opencode", "plugin", "harnesstrim.ts");
|
|
22721
|
+
const wrapperPresent = fs.existsSync(wrapper);
|
|
22722
|
+
const raw = readIfExists(path.join(dir, "opencode.json"));
|
|
22723
|
+
let staleEntry = false;
|
|
22724
|
+
if (raw !== null) {
|
|
22725
|
+
try {
|
|
22726
|
+
staleEntry = extractPluginNames(JSON.parse(raw)).some((p) => p.includes("@harnesstrim/adapter-opencode"));
|
|
22727
|
+
} catch {
|
|
22728
|
+
}
|
|
22703
22729
|
}
|
|
22704
|
-
|
|
22705
|
-
const installed = plugins.some((p) => p.includes("@harnesstrim/adapter-opencode"));
|
|
22706
|
-
if (installed) {
|
|
22730
|
+
if (wrapperPresent) {
|
|
22707
22731
|
findings.push({
|
|
22708
22732
|
severity: "ok",
|
|
22709
|
-
title: "HarnessTrim OpenCode adapter is
|
|
22710
|
-
detail: "
|
|
22733
|
+
title: "HarnessTrim OpenCode adapter is installed",
|
|
22734
|
+
detail: ".opencode/plugin/harnesstrim.ts loads the adapter (OpenCode auto-loads it)."
|
|
22711
22735
|
});
|
|
22712
22736
|
} else {
|
|
22713
22737
|
findings.push({
|
|
22714
22738
|
severity: "warn",
|
|
22715
|
-
title: "HarnessTrim adapter not installed
|
|
22739
|
+
title: "HarnessTrim adapter not installed for OpenCode",
|
|
22716
22740
|
detail: "The OpenCode adapter would slim tool output automatically.",
|
|
22717
|
-
suggestion: "Run `harnesstrim install opencode` to
|
|
22741
|
+
suggestion: "Run `harnesstrim install opencode --apply` to install the local plugin."
|
|
22742
|
+
});
|
|
22743
|
+
}
|
|
22744
|
+
if (staleEntry && !wrapperPresent) {
|
|
22745
|
+
findings.push({
|
|
22746
|
+
severity: "warn",
|
|
22747
|
+
title: "Stale adapter entry in opencode.json",
|
|
22748
|
+
detail: "opencode.json lists @harnesstrim/adapter-opencode, but OpenCode's plugin field is a string array and ignores option tuples \u2014 this entry never loaded.",
|
|
22749
|
+
suggestion: "Run `harnesstrim install opencode --apply` to migrate to the local plugin wrapper."
|
|
22718
22750
|
});
|
|
22719
22751
|
}
|
|
22720
22752
|
}
|
|
@@ -22741,70 +22773,151 @@ function inspect(dir) {
|
|
|
22741
22773
|
init_src();
|
|
22742
22774
|
import fs2 from "node:fs";
|
|
22743
22775
|
import path2 from "node:path";
|
|
22776
|
+
import { spawnSync } from "node:child_process";
|
|
22744
22777
|
var OPENCODE_PLUGIN_NAME = "@harnesstrim/adapter-opencode";
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
22778
|
+
var OPENCODE_ADAPTER_VERSION = "^0.0.2";
|
|
22779
|
+
var DEFAULT_OPENCODE_ADAPTER_CONFIG = {
|
|
22780
|
+
mode: "active",
|
|
22781
|
+
telemetry: true,
|
|
22782
|
+
telemetryPath: ".harnesstrim/metrics.jsonl"
|
|
22783
|
+
};
|
|
22784
|
+
var WRAPPER_REL = path2.join(".opencode", "plugin", "harnesstrim.ts");
|
|
22785
|
+
var PKG_REL = path2.join(".opencode", "package.json");
|
|
22786
|
+
var OPENCODE_JSON = "opencode.json";
|
|
22787
|
+
function buildOpencodeWrapper(adapterConfig) {
|
|
22788
|
+
const opts = JSON.stringify(adapterConfig, null, 2).replace(/\n/g, "\n ");
|
|
22789
|
+
return `// Generated by \`harnesstrim install opencode\`. Do not reference this plugin from
|
|
22790
|
+
// opencode.json \u2014 OpenCode's \`plugin\` field is a string array and cannot pass options,
|
|
22791
|
+
// so HarnessTrim options live here and OpenCode auto-loads this file from .opencode/plugin/.
|
|
22792
|
+
// The dependency is declared in ../package.json. Edit the options below freely.
|
|
22793
|
+
import { HarnessTrim } from "${OPENCODE_PLUGIN_NAME}";
|
|
22794
|
+
|
|
22795
|
+
export const HarnessTrimPlugin = async (input) =>
|
|
22796
|
+
HarnessTrim(input, ${opts});
|
|
22797
|
+
`;
|
|
22749
22798
|
}
|
|
22750
|
-
function
|
|
22751
|
-
|
|
22752
|
-
|
|
22753
|
-
|
|
22799
|
+
function buildOpencodePackageJson(existing) {
|
|
22800
|
+
let base = {};
|
|
22801
|
+
if (existing) {
|
|
22802
|
+
try {
|
|
22803
|
+
const parsed = JSON.parse(existing);
|
|
22804
|
+
if (parsed && typeof parsed === "object") base = parsed;
|
|
22805
|
+
} catch {
|
|
22806
|
+
}
|
|
22807
|
+
}
|
|
22808
|
+
const deps = { ...base.dependencies };
|
|
22809
|
+
deps[OPENCODE_PLUGIN_NAME] = OPENCODE_ADAPTER_VERSION;
|
|
22810
|
+
return JSON.stringify({ ...base, dependencies: deps }, null, 2) + "\n";
|
|
22811
|
+
}
|
|
22812
|
+
function opencodeJsonReferencesAdapter(config2) {
|
|
22813
|
+
const plugin = config2.plugin;
|
|
22814
|
+
if (!Array.isArray(plugin)) return false;
|
|
22815
|
+
return plugin.some((e) => {
|
|
22816
|
+
if (typeof e === "string") return e.includes(OPENCODE_PLUGIN_NAME);
|
|
22817
|
+
if (Array.isArray(e)) return typeof e[0] === "string" && e[0].includes(OPENCODE_PLUGIN_NAME);
|
|
22818
|
+
if (e && typeof e === "object") {
|
|
22819
|
+
const name = e.name;
|
|
22820
|
+
return typeof name === "string" && name.includes(OPENCODE_PLUGIN_NAME);
|
|
22821
|
+
}
|
|
22822
|
+
return false;
|
|
22823
|
+
});
|
|
22754
22824
|
}
|
|
22755
|
-
function
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
22775
|
-
return {
|
|
22776
|
-
}
|
|
22777
|
-
function
|
|
22825
|
+
function cleanOpencodeJson(existing) {
|
|
22826
|
+
let config2;
|
|
22827
|
+
try {
|
|
22828
|
+
config2 = JSON.parse(existing);
|
|
22829
|
+
} catch {
|
|
22830
|
+
throw new Error(`${OPENCODE_JSON} exists but is not valid JSON`);
|
|
22831
|
+
}
|
|
22832
|
+
if (!opencodeJsonReferencesAdapter(config2)) return null;
|
|
22833
|
+
const plugin = config2.plugin.filter((e) => {
|
|
22834
|
+
if (typeof e === "string") return !e.includes(OPENCODE_PLUGIN_NAME);
|
|
22835
|
+
if (Array.isArray(e)) return !(typeof e[0] === "string" && e[0].includes(OPENCODE_PLUGIN_NAME));
|
|
22836
|
+
if (e && typeof e === "object") {
|
|
22837
|
+
const name = e.name;
|
|
22838
|
+
return !(typeof name === "string" && name.includes(OPENCODE_PLUGIN_NAME));
|
|
22839
|
+
}
|
|
22840
|
+
return true;
|
|
22841
|
+
});
|
|
22842
|
+
const next = { ...config2 };
|
|
22843
|
+
if (plugin.length === 0) delete next.plugin;
|
|
22844
|
+
else next.plugin = plugin;
|
|
22845
|
+
return { content: JSON.stringify(next, null, 2) + "\n" };
|
|
22846
|
+
}
|
|
22847
|
+
function planOpencodeInstall(input) {
|
|
22848
|
+
const adapterConfig = input.adapterConfig ?? DEFAULT_OPENCODE_ADAPTER_CONFIG;
|
|
22849
|
+
const wrapperContent = buildOpencodeWrapper(adapterConfig);
|
|
22850
|
+
const packageJsonContent = buildOpencodePackageJson(input.existingPackageJson);
|
|
22851
|
+
const opencodeClean = input.existingOpencodeJson ? cleanOpencodeJson(input.existingOpencodeJson) : null;
|
|
22852
|
+
const opencodeJsonContent = opencodeClean?.content ?? null;
|
|
22853
|
+
const wrapperChanged = input.existingWrapper !== wrapperContent;
|
|
22854
|
+
const pkgChanged = input.existingPackageJson !== packageJsonContent;
|
|
22855
|
+
const opencodeChanged = opencodeJsonContent !== null;
|
|
22856
|
+
const alreadyInstalled = input.existingWrapper !== null;
|
|
22857
|
+
const changed = wrapperChanged || pkgChanged || opencodeChanged;
|
|
22858
|
+
return { wrapperContent, packageJsonContent, opencodeJsonContent, alreadyInstalled, changed };
|
|
22859
|
+
}
|
|
22860
|
+
function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
22778
22861
|
let preset;
|
|
22779
|
-
let adapterConfig;
|
|
22862
|
+
let adapterConfig = { ...DEFAULT_OPENCODE_ADAPTER_CONFIG };
|
|
22780
22863
|
if (presetName) {
|
|
22781
22864
|
preset = getPreset(presetName);
|
|
22782
22865
|
if (!preset) throw new Error(`Unknown preset: ${presetName}`);
|
|
22783
|
-
adapterConfig = { ...preset.adapter };
|
|
22784
|
-
}
|
|
22785
|
-
const configPath = path2.join(dir, "opencode.json");
|
|
22786
|
-
let raw = null;
|
|
22787
|
-
try {
|
|
22788
|
-
raw = fs2.readFileSync(configPath, "utf8");
|
|
22789
|
-
} catch {
|
|
22790
|
-
raw = null;
|
|
22866
|
+
adapterConfig = { ...adapterConfig, ...preset.adapter };
|
|
22791
22867
|
}
|
|
22792
|
-
const
|
|
22793
|
-
|
|
22794
|
-
|
|
22868
|
+
const wrapperPath = path2.join(dir, WRAPPER_REL);
|
|
22869
|
+
const packageJsonPath = path2.join(dir, PKG_REL);
|
|
22870
|
+
const opencodeJsonPath = path2.join(dir, OPENCODE_JSON);
|
|
22871
|
+
const readOrNull = (p) => {
|
|
22795
22872
|
try {
|
|
22796
|
-
|
|
22873
|
+
return fs2.readFileSync(p, "utf8");
|
|
22797
22874
|
} catch {
|
|
22798
|
-
|
|
22875
|
+
return null;
|
|
22799
22876
|
}
|
|
22800
|
-
}
|
|
22801
|
-
const
|
|
22877
|
+
};
|
|
22878
|
+
const existingWrapper = readOrNull(wrapperPath);
|
|
22879
|
+
const existingPackageJson = readOrNull(packageJsonPath);
|
|
22880
|
+
const existingOpencodeJson = readOrNull(opencodeJsonPath);
|
|
22881
|
+
const plan = planOpencodeInstall({
|
|
22882
|
+
existingWrapper,
|
|
22883
|
+
existingPackageJson,
|
|
22884
|
+
existingOpencodeJson,
|
|
22885
|
+
adapterConfig
|
|
22886
|
+
});
|
|
22802
22887
|
let applied = false;
|
|
22888
|
+
let depsInstalled = null;
|
|
22889
|
+
let depsMessage;
|
|
22803
22890
|
if (apply && plan.changed) {
|
|
22804
|
-
fs2.
|
|
22891
|
+
fs2.mkdirSync(path2.dirname(wrapperPath), { recursive: true });
|
|
22892
|
+
fs2.writeFileSync(wrapperPath, plan.wrapperContent);
|
|
22893
|
+
fs2.writeFileSync(packageJsonPath, plan.packageJsonContent);
|
|
22894
|
+
if (plan.opencodeJsonContent !== null) fs2.writeFileSync(opencodeJsonPath, plan.opencodeJsonContent);
|
|
22805
22895
|
applied = true;
|
|
22896
|
+
if (installDeps) {
|
|
22897
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
22898
|
+
const res = spawnSync(npm, ["install", "--silent"], {
|
|
22899
|
+
cwd: path2.dirname(packageJsonPath),
|
|
22900
|
+
encoding: "utf8"
|
|
22901
|
+
});
|
|
22902
|
+
if (res.error || res.status !== 0) {
|
|
22903
|
+
depsInstalled = false;
|
|
22904
|
+
depsMessage = `Could not auto-install the adapter; run \`npm install\` in ${path2.dirname(packageJsonPath)}.`;
|
|
22905
|
+
} else {
|
|
22906
|
+
depsInstalled = true;
|
|
22907
|
+
}
|
|
22908
|
+
}
|
|
22806
22909
|
}
|
|
22807
|
-
return {
|
|
22910
|
+
return {
|
|
22911
|
+
...plan,
|
|
22912
|
+
wrapperPath,
|
|
22913
|
+
packageJsonPath,
|
|
22914
|
+
opencodeJsonPath,
|
|
22915
|
+
existed: existingWrapper !== null,
|
|
22916
|
+
applied,
|
|
22917
|
+
depsInstalled,
|
|
22918
|
+
depsMessage,
|
|
22919
|
+
preset
|
|
22920
|
+
};
|
|
22808
22921
|
}
|
|
22809
22922
|
|
|
22810
22923
|
// src/install-codex.ts
|
|
@@ -23101,6 +23214,19 @@ function extractOutputText2(p) {
|
|
|
23101
23214
|
import path7 from "node:path";
|
|
23102
23215
|
var HOOK_COMMAND = "harnesstrim hook claude";
|
|
23103
23216
|
var HOOK_MATCHER = "Bash";
|
|
23217
|
+
var HARNESSTRIM_MARKER2 = "harnesstrim:begin";
|
|
23218
|
+
var REDUCE_INSTRUCTION_SNIPPET2 = `<!-- ${HARNESSTRIM_MARKER2} -->
|
|
23219
|
+
## Token economy (HarnessTrim)
|
|
23220
|
+
|
|
23221
|
+
When a shell command produces long, noisy output \u2014 test runners, \`git diff\`, build logs,
|
|
23222
|
+
large file dumps \u2014 pipe it through the reducer so only the signal enters context:
|
|
23223
|
+
|
|
23224
|
+
<your command> 2>&1 | harnesstrim reduce --metrics .harnesstrim/metrics.jsonl
|
|
23225
|
+
|
|
23226
|
+
This keeps failures, errors, assertions, and summaries while dropping passing-test noise and
|
|
23227
|
+
generated-file (lockfile/dist) diffs, and records what was saved. Prefer the installed skills
|
|
23228
|
+
for output, review, and scaffolding discipline.
|
|
23229
|
+
<!-- harnesstrim:end -->`;
|
|
23104
23230
|
function hasHarnessTrimHook2(settings) {
|
|
23105
23231
|
const hooks = settings.hooks;
|
|
23106
23232
|
const post = hooks?.PostToolUse;
|
|
@@ -23132,12 +23258,16 @@ function planClaudeInstall(input) {
|
|
|
23132
23258
|
action = hasHarnessTrimHook2(settings) ? "present" : "patch";
|
|
23133
23259
|
}
|
|
23134
23260
|
const nextSettings = action === "present" ? settings : addHook(settings);
|
|
23261
|
+
const instructionsAction = input.claudeMdContent === null ? "create" : input.claudeMdContent.includes(HARNESSTRIM_MARKER2) ? "present" : "append";
|
|
23135
23262
|
return {
|
|
23136
23263
|
skillsDest,
|
|
23137
23264
|
skills,
|
|
23138
23265
|
settingsFile: path7.join(input.projectDir, ".claude", "settings.json"),
|
|
23139
23266
|
settingsAction: action,
|
|
23140
|
-
nextSettings
|
|
23267
|
+
nextSettings,
|
|
23268
|
+
instructionsFile: path7.join(input.projectDir, "CLAUDE.md"),
|
|
23269
|
+
instructionsAction,
|
|
23270
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2
|
|
23141
23271
|
};
|
|
23142
23272
|
}
|
|
23143
23273
|
function addHook(settings) {
|
|
@@ -23162,11 +23292,19 @@ function runInstallClaude(dir, apply) {
|
|
|
23162
23292
|
} catch {
|
|
23163
23293
|
settingsJsonContent = null;
|
|
23164
23294
|
}
|
|
23295
|
+
const claudeMdPath = path8.join(dir, "CLAUDE.md");
|
|
23296
|
+
let claudeMdContent = null;
|
|
23297
|
+
try {
|
|
23298
|
+
claudeMdContent = fs6.readFileSync(claudeMdPath, "utf8");
|
|
23299
|
+
} catch {
|
|
23300
|
+
claudeMdContent = null;
|
|
23301
|
+
}
|
|
23165
23302
|
const plan = planClaudeInstall({
|
|
23166
23303
|
projectDir: dir,
|
|
23167
23304
|
skillsSourceDir,
|
|
23168
23305
|
skillNames,
|
|
23169
23306
|
settingsJsonContent,
|
|
23307
|
+
claudeMdContent,
|
|
23170
23308
|
existingSkillNames: existingSkillNames(skillsDest)
|
|
23171
23309
|
});
|
|
23172
23310
|
const copied = [];
|
|
@@ -23181,6 +23319,11 @@ function runInstallClaude(dir, apply) {
|
|
|
23181
23319
|
fs6.mkdirSync(path8.dirname(plan.settingsFile), { recursive: true });
|
|
23182
23320
|
fs6.writeFileSync(plan.settingsFile, JSON.stringify(plan.nextSettings, null, 2) + "\n");
|
|
23183
23321
|
}
|
|
23322
|
+
if (plan.instructionsAction === "create") {
|
|
23323
|
+
fs6.writeFileSync(plan.instructionsFile, plan.instructionsSnippet + "\n");
|
|
23324
|
+
} else if (plan.instructionsAction === "append") {
|
|
23325
|
+
fs6.appendFileSync(plan.instructionsFile, "\n\n" + plan.instructionsSnippet + "\n");
|
|
23326
|
+
}
|
|
23184
23327
|
applied = true;
|
|
23185
23328
|
}
|
|
23186
23329
|
return { plan, applied, copied };
|
|
@@ -23256,7 +23399,7 @@ function runInstallPi(installDir, apply) {
|
|
|
23256
23399
|
// src/install-hermes.ts
|
|
23257
23400
|
import fs8 from "node:fs";
|
|
23258
23401
|
import path12 from "node:path";
|
|
23259
|
-
import { spawnSync } from "node:child_process";
|
|
23402
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
23260
23403
|
|
|
23261
23404
|
// ../adapter-hermes/src/index.ts
|
|
23262
23405
|
import path11 from "node:path";
|
|
@@ -23323,7 +23466,7 @@ function runInstallHermes(installDir, apply) {
|
|
|
23323
23466
|
fs8.writeFileSync(path12.join(pluginDest, ".installed"), markerFileContent2());
|
|
23324
23467
|
copiedFiles.push(".installed");
|
|
23325
23468
|
applied = true;
|
|
23326
|
-
const enable =
|
|
23469
|
+
const enable = spawnSync2("hermes", ["plugins", "enable", HERMES_PLUGIN_NAME], {
|
|
23327
23470
|
encoding: "utf8"
|
|
23328
23471
|
});
|
|
23329
23472
|
if (enable.error) {
|
|
@@ -23389,19 +23532,38 @@ function renderDoctor(report) {
|
|
|
23389
23532
|
}
|
|
23390
23533
|
function renderInstall(result, apply) {
|
|
23391
23534
|
const lines = [];
|
|
23392
|
-
|
|
23393
|
-
|
|
23535
|
+
const wrapper = result.wrapperPath;
|
|
23536
|
+
const pkg = result.packageJsonPath;
|
|
23537
|
+
if (!result.changed) {
|
|
23538
|
+
lines.push(`Already installed and up to date: ${wrapper} loads the HarnessTrim adapter.`);
|
|
23539
|
+
if (result.preset) {
|
|
23540
|
+
lines.push("");
|
|
23541
|
+
lines.push(renderPresetAdvisory(result.preset));
|
|
23542
|
+
}
|
|
23394
23543
|
return lines.join("\n");
|
|
23395
23544
|
}
|
|
23396
23545
|
if (apply) {
|
|
23397
|
-
lines.push(`
|
|
23398
|
-
lines.push(
|
|
23399
|
-
}
|
|
23400
|
-
|
|
23546
|
+
lines.push(`Installed the OpenCode adapter as a local plugin:`);
|
|
23547
|
+
lines.push(` \u2022 wrote ${wrapper} (auto-loaded by OpenCode from .opencode/plugin/)`);
|
|
23548
|
+
lines.push(` \u2022 wrote ${pkg} (declares @harnesstrim/adapter-opencode)`);
|
|
23549
|
+
if (result.opencodeJsonContent !== null) {
|
|
23550
|
+
lines.push(` \u2022 cleaned the adapter entry out of ${result.opencodeJsonPath} (options live in the wrapper now)`);
|
|
23551
|
+
}
|
|
23552
|
+
if (result.depsInstalled === true) lines.push(` \u2022 installed the .opencode dependency`);
|
|
23553
|
+
else if (result.depsInstalled === false) lines.push(` ! ${result.depsMessage}`);
|
|
23401
23554
|
lines.push("");
|
|
23402
|
-
lines.push(
|
|
23555
|
+
lines.push("Reduction is active; telemetry writes to .harnesstrim/metrics.jsonl.");
|
|
23556
|
+
lines.push("Reload OpenCode so it loads the plugin, then check `harnesstrim metrics`.");
|
|
23557
|
+
} else {
|
|
23558
|
+
lines.push(`Dry run \u2014 no files changed. \`--apply\` would:`);
|
|
23559
|
+
lines.push(` \u2022 write ${wrapper} (local plugin wrapper with the adapter options)`);
|
|
23560
|
+
lines.push(` \u2022 write ${pkg} (declare @harnesstrim/adapter-opencode) and install it`);
|
|
23561
|
+
if (result.opencodeJsonContent !== null) {
|
|
23562
|
+
lines.push(` \u2022 remove the stale adapter entry from ${result.opencodeJsonPath}`);
|
|
23563
|
+
}
|
|
23403
23564
|
lines.push("");
|
|
23404
|
-
lines.push("
|
|
23565
|
+
lines.push("OpenCode's `plugin` config can't pass options, so the adapter is installed as a");
|
|
23566
|
+
lines.push("local plugin file instead. Re-run with `--apply` to write it.");
|
|
23405
23567
|
}
|
|
23406
23568
|
if (result.preset) {
|
|
23407
23569
|
lines.push("");
|
|
@@ -23514,7 +23676,19 @@ function renderClaudeInstall(result, apply) {
|
|
|
23514
23676
|
}
|
|
23515
23677
|
}
|
|
23516
23678
|
lines.push("");
|
|
23517
|
-
|
|
23679
|
+
if (plan.instructionsAction === "present") {
|
|
23680
|
+
lines.push(`${plan.instructionsFile}: reduce-pipe instruction already present (no change).`);
|
|
23681
|
+
} else {
|
|
23682
|
+
lines.push(
|
|
23683
|
+
`${plan.instructionsFile}: reduce-pipe instruction ${apply ? plan.instructionsAction === "create" ? "created" : "appended" : "would be added"}.`
|
|
23684
|
+
);
|
|
23685
|
+
}
|
|
23686
|
+
lines.push("");
|
|
23687
|
+
lines.push("Note: `harnesstrim` must be on PATH (used by both the hook and the reduce pipe).");
|
|
23688
|
+
lines.push(
|
|
23689
|
+
"The CLAUDE.md instruction is the effective path today: current Claude Code versions don't apply"
|
|
23690
|
+
);
|
|
23691
|
+
lines.push("the hook's updatedToolOutput, so piping through `harnesstrim reduce` is what saves tokens.");
|
|
23518
23692
|
if (!apply) lines.push("Dry run \u2014 nothing written. Re-run with `--apply`.");
|
|
23519
23693
|
return lines.join("\n");
|
|
23520
23694
|
}
|
|
@@ -23618,8 +23792,11 @@ Usage:
|
|
|
23618
23792
|
harnesstrim preset list List policy presets
|
|
23619
23793
|
harnesstrim preset show <name> Show a preset in detail
|
|
23620
23794
|
harnesstrim metrics [path] Summarize adapter telemetry (JSONL)
|
|
23621
|
-
harnesstrim reduce [--stats]
|
|
23622
|
-
|
|
23795
|
+
harnesstrim reduce [--stats] [--metrics <path>]
|
|
23796
|
+
Slim stdin -> stdout (pipe noisy command output);
|
|
23797
|
+
--metrics records a TrimEvent per reduction
|
|
23798
|
+
harnesstrim mcp [--metrics <path>] Start the MCP server (stdio) exposing a reduce tool;
|
|
23799
|
+
--metrics records a TrimEvent per reduction
|
|
23623
23800
|
harnesstrim bench Run the Tier A reducer micro-benchmark
|
|
23624
23801
|
harnesstrim --help Show this help
|
|
23625
23802
|
|
|
@@ -23669,7 +23846,7 @@ async function main(argv) {
|
|
|
23669
23846
|
console.error("`harnesstrim install codex --global` requires `--hook`.");
|
|
23670
23847
|
return 1;
|
|
23671
23848
|
}
|
|
23672
|
-
console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(
|
|
23849
|
+
console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path15.join(os2.homedir(), ".codex"), apply), apply));
|
|
23673
23850
|
return 0;
|
|
23674
23851
|
}
|
|
23675
23852
|
console.log(renderCodexInstall(runInstallCodex(dir, apply, values.hook === true), apply));
|
|
@@ -23701,9 +23878,9 @@ async function main(argv) {
|
|
|
23701
23878
|
process.stdout.write(response);
|
|
23702
23879
|
if (values.metrics && event) {
|
|
23703
23880
|
try {
|
|
23704
|
-
const p =
|
|
23705
|
-
|
|
23706
|
-
|
|
23881
|
+
const p = path15.resolve(values.metrics);
|
|
23882
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
23883
|
+
fs11.appendFileSync(
|
|
23707
23884
|
p,
|
|
23708
23885
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), harness: which, ...event }) + "\n"
|
|
23709
23886
|
);
|
|
@@ -23712,7 +23889,7 @@ async function main(argv) {
|
|
|
23712
23889
|
}
|
|
23713
23890
|
if (values.log) {
|
|
23714
23891
|
try {
|
|
23715
|
-
|
|
23892
|
+
fs11.appendFileSync(
|
|
23716
23893
|
values.log,
|
|
23717
23894
|
JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
|
|
23718
23895
|
);
|
|
@@ -23741,8 +23918,8 @@ async function main(argv) {
|
|
|
23741
23918
|
return 1;
|
|
23742
23919
|
}
|
|
23743
23920
|
case "metrics": {
|
|
23744
|
-
const
|
|
23745
|
-
console.log(renderMetrics(loadMetrics(
|
|
23921
|
+
const path16 = rest[0] ?? DEFAULT_METRICS_PATH;
|
|
23922
|
+
console.log(renderMetrics(loadMetrics(path16)));
|
|
23746
23923
|
return 0;
|
|
23747
23924
|
}
|
|
23748
23925
|
case "reduce": {
|
|
@@ -23755,6 +23932,24 @@ async function main(argv) {
|
|
|
23755
23932
|
const input = await readStdin();
|
|
23756
23933
|
const result = reducePipe(input, minLength);
|
|
23757
23934
|
process.stdout.write(result.output);
|
|
23935
|
+
if (values.metrics && result.changed) {
|
|
23936
|
+
try {
|
|
23937
|
+
const p = path15.resolve(values.metrics);
|
|
23938
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
23939
|
+
fs11.appendFileSync(
|
|
23940
|
+
p,
|
|
23941
|
+
JSON.stringify({
|
|
23942
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23943
|
+
harness: "pipe",
|
|
23944
|
+
tool: "reduce",
|
|
23945
|
+
reducer: result.reducer,
|
|
23946
|
+
beforeChars: result.beforeChars,
|
|
23947
|
+
afterChars: result.afterChars
|
|
23948
|
+
}) + "\n"
|
|
23949
|
+
);
|
|
23950
|
+
} catch {
|
|
23951
|
+
}
|
|
23952
|
+
}
|
|
23758
23953
|
if (values.stats) {
|
|
23759
23954
|
const note = result.changed ? `${result.reducer}: ${result.beforeChars} -> ${result.afterChars} chars` : "no reduction (no reducer matched or below min-length)";
|
|
23760
23955
|
console.error(`[harnesstrim reduce] ${note}`);
|
|
@@ -23763,7 +23958,7 @@ async function main(argv) {
|
|
|
23763
23958
|
}
|
|
23764
23959
|
case "mcp": {
|
|
23765
23960
|
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
|
|
23766
|
-
await startStdioServer2();
|
|
23961
|
+
await startStdioServer2(values.metrics ? { metricsPath: values.metrics } : {});
|
|
23767
23962
|
await new Promise(() => {
|
|
23768
23963
|
});
|
|
23769
23964
|
return 0;
|