harnesstrim 0.0.4 → 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.
Files changed (2) hide show
  1. package/dist/cli.mjs +125 -35
  2. 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: path15, errorMaps, issueData } = params;
1170
- const fullPath = [...path15, ...issueData.path || []];
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, path15, key) {
1478
+ constructor(parent, value, path16, key) {
1479
1479
  this._cachedPath = [];
1480
1480
  this.parent = parent;
1481
1481
  this.data = value;
1482
- this._path = path15;
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, path15) {
5064
- if (!path15)
5063
+ function getElementAtPath(obj, path16) {
5064
+ if (!path16)
5065
5065
  return obj;
5066
- return path15.reduce((acc, key) => acc?.[key], obj);
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(path15, issues) {
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(path15);
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(path15) {
17150
- let input = path15;
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 [path15, query] = wsComponent.resourceName.split("?");
17403
- wsComponent.path = path15 && path15 !== "/" ? path15 : void 0;
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, fs11, exportName) {
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, fs11[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
- function runReduceTool(text, minLength) {
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 server = createServer();
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 fs10 from "node:fs";
22602
- import path14 from "node:path";
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
@@ -23187,6 +23214,19 @@ function extractOutputText2(p) {
23187
23214
  import path7 from "node:path";
23188
23215
  var HOOK_COMMAND = "harnesstrim hook claude";
23189
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 -->`;
23190
23230
  function hasHarnessTrimHook2(settings) {
23191
23231
  const hooks = settings.hooks;
23192
23232
  const post = hooks?.PostToolUse;
@@ -23218,12 +23258,16 @@ function planClaudeInstall(input) {
23218
23258
  action = hasHarnessTrimHook2(settings) ? "present" : "patch";
23219
23259
  }
23220
23260
  const nextSettings = action === "present" ? settings : addHook(settings);
23261
+ const instructionsAction = input.claudeMdContent === null ? "create" : input.claudeMdContent.includes(HARNESSTRIM_MARKER2) ? "present" : "append";
23221
23262
  return {
23222
23263
  skillsDest,
23223
23264
  skills,
23224
23265
  settingsFile: path7.join(input.projectDir, ".claude", "settings.json"),
23225
23266
  settingsAction: action,
23226
- nextSettings
23267
+ nextSettings,
23268
+ instructionsFile: path7.join(input.projectDir, "CLAUDE.md"),
23269
+ instructionsAction,
23270
+ instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2
23227
23271
  };
23228
23272
  }
23229
23273
  function addHook(settings) {
@@ -23248,11 +23292,19 @@ function runInstallClaude(dir, apply) {
23248
23292
  } catch {
23249
23293
  settingsJsonContent = null;
23250
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
+ }
23251
23302
  const plan = planClaudeInstall({
23252
23303
  projectDir: dir,
23253
23304
  skillsSourceDir,
23254
23305
  skillNames,
23255
23306
  settingsJsonContent,
23307
+ claudeMdContent,
23256
23308
  existingSkillNames: existingSkillNames(skillsDest)
23257
23309
  });
23258
23310
  const copied = [];
@@ -23267,6 +23319,11 @@ function runInstallClaude(dir, apply) {
23267
23319
  fs6.mkdirSync(path8.dirname(plan.settingsFile), { recursive: true });
23268
23320
  fs6.writeFileSync(plan.settingsFile, JSON.stringify(plan.nextSettings, null, 2) + "\n");
23269
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
+ }
23270
23327
  applied = true;
23271
23328
  }
23272
23329
  return { plan, applied, copied };
@@ -23619,7 +23676,19 @@ function renderClaudeInstall(result, apply) {
23619
23676
  }
23620
23677
  }
23621
23678
  lines.push("");
23622
- lines.push("Note: the hook command is `harnesstrim hook claude` \u2014 ensure harnesstrim is on PATH.");
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.");
23623
23692
  if (!apply) lines.push("Dry run \u2014 nothing written. Re-run with `--apply`.");
23624
23693
  return lines.join("\n");
23625
23694
  }
@@ -23723,8 +23792,11 @@ Usage:
23723
23792
  harnesstrim preset list List policy presets
23724
23793
  harnesstrim preset show <name> Show a preset in detail
23725
23794
  harnesstrim metrics [path] Summarize adapter telemetry (JSONL)
23726
- harnesstrim reduce [--stats] Slim stdin -> stdout (pipe noisy command output)
23727
- harnesstrim mcp Start the MCP server (stdio) exposing a reduce tool
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
23728
23800
  harnesstrim bench Run the Tier A reducer micro-benchmark
23729
23801
  harnesstrim --help Show this help
23730
23802
 
@@ -23774,7 +23846,7 @@ async function main(argv) {
23774
23846
  console.error("`harnesstrim install codex --global` requires `--hook`.");
23775
23847
  return 1;
23776
23848
  }
23777
- console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path14.join(os2.homedir(), ".codex"), apply), apply));
23849
+ console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path15.join(os2.homedir(), ".codex"), apply), apply));
23778
23850
  return 0;
23779
23851
  }
23780
23852
  console.log(renderCodexInstall(runInstallCodex(dir, apply, values.hook === true), apply));
@@ -23806,9 +23878,9 @@ async function main(argv) {
23806
23878
  process.stdout.write(response);
23807
23879
  if (values.metrics && event) {
23808
23880
  try {
23809
- const p = path14.resolve(values.metrics);
23810
- fs10.mkdirSync(path14.dirname(p), { recursive: true });
23811
- fs10.appendFileSync(
23881
+ const p = path15.resolve(values.metrics);
23882
+ fs11.mkdirSync(path15.dirname(p), { recursive: true });
23883
+ fs11.appendFileSync(
23812
23884
  p,
23813
23885
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), harness: which, ...event }) + "\n"
23814
23886
  );
@@ -23817,7 +23889,7 @@ async function main(argv) {
23817
23889
  }
23818
23890
  if (values.log) {
23819
23891
  try {
23820
- fs10.appendFileSync(
23892
+ fs11.appendFileSync(
23821
23893
  values.log,
23822
23894
  JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
23823
23895
  );
@@ -23846,8 +23918,8 @@ async function main(argv) {
23846
23918
  return 1;
23847
23919
  }
23848
23920
  case "metrics": {
23849
- const path15 = rest[0] ?? DEFAULT_METRICS_PATH;
23850
- console.log(renderMetrics(loadMetrics(path15)));
23921
+ const path16 = rest[0] ?? DEFAULT_METRICS_PATH;
23922
+ console.log(renderMetrics(loadMetrics(path16)));
23851
23923
  return 0;
23852
23924
  }
23853
23925
  case "reduce": {
@@ -23860,6 +23932,24 @@ async function main(argv) {
23860
23932
  const input = await readStdin();
23861
23933
  const result = reducePipe(input, minLength);
23862
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
+ }
23863
23953
  if (values.stats) {
23864
23954
  const note = result.changed ? `${result.reducer}: ${result.beforeChars} -> ${result.afterChars} chars` : "no reduction (no reducer matched or below min-length)";
23865
23955
  console.error(`[harnesstrim reduce] ${note}`);
@@ -23868,7 +23958,7 @@ async function main(argv) {
23868
23958
  }
23869
23959
  case "mcp": {
23870
23960
  const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
23871
- await startStdioServer2();
23961
+ await startStdioServer2(values.metrics ? { metricsPath: values.metrics } : {});
23872
23962
  await new Promise(() => {
23873
23963
  });
23874
23964
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnesstrim",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
5
5
  "license": "MIT",
6
6
  "type": "module",