harnesstrim 0.0.2 → 0.0.3
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 +186 -15
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -22747,11 +22747,23 @@ function pluginIndex(plugin) {
|
|
|
22747
22747
|
(e) => typeof e === "string" && e.includes(OPENCODE_PLUGIN_NAME) || Array.isArray(e) && typeof e[0] === "string" && e[0].includes(OPENCODE_PLUGIN_NAME)
|
|
22748
22748
|
);
|
|
22749
22749
|
}
|
|
22750
|
+
function legacyPluginIndex(plugin) {
|
|
22751
|
+
return plugin.findIndex(
|
|
22752
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.name === "string" && entry.name.includes(OPENCODE_PLUGIN_NAME)
|
|
22753
|
+
);
|
|
22754
|
+
}
|
|
22750
22755
|
function planOpencodeInstall(config2, adapterConfig) {
|
|
22751
22756
|
const base = typeof config2 === "object" && config2 !== null ? { ...config2 } : {};
|
|
22752
22757
|
const plugin = Array.isArray(base.plugin) ? [...base.plugin] : [];
|
|
22753
22758
|
const desired = adapterConfig ? [OPENCODE_PLUGIN_NAME, adapterConfig] : OPENCODE_PLUGIN_NAME;
|
|
22754
22759
|
const idx = pluginIndex(plugin);
|
|
22760
|
+
const legacyIdx = idx === -1 ? legacyPluginIndex(plugin) : -1;
|
|
22761
|
+
if (legacyIdx !== -1) {
|
|
22762
|
+
const legacy = plugin[legacyIdx];
|
|
22763
|
+
const options = legacy.options;
|
|
22764
|
+
plugin[legacyIdx] = adapterConfig ? desired : typeof options === "object" && options !== null && !Array.isArray(options) ? [OPENCODE_PLUGIN_NAME, options] : OPENCODE_PLUGIN_NAME;
|
|
22765
|
+
return { nextConfig: { ...base, plugin }, alreadyInstalled: true, changed: true };
|
|
22766
|
+
}
|
|
22755
22767
|
if (idx === -1) {
|
|
22756
22768
|
plugin.push(desired);
|
|
22757
22769
|
return { nextConfig: { ...base, plugin }, alreadyInstalled: false, changed: true };
|
|
@@ -22801,7 +22813,56 @@ import path6 from "node:path";
|
|
|
22801
22813
|
|
|
22802
22814
|
// ../adapter-codex/src/index.ts
|
|
22803
22815
|
import path3 from "node:path";
|
|
22816
|
+
|
|
22817
|
+
// ../adapter-codex/src/hook.ts
|
|
22818
|
+
init_src();
|
|
22819
|
+
function reduceCodexPayload(rawJson, minLength) {
|
|
22820
|
+
const extracted = extractToolOutput(rawJson);
|
|
22821
|
+
if (extracted === null) return { response: "{}", event: null };
|
|
22822
|
+
const result = reduceAuto(extracted.output, minLength);
|
|
22823
|
+
if (!result.changed) return { response: "{}", event: null };
|
|
22824
|
+
const response = JSON.stringify({
|
|
22825
|
+
decision: "block",
|
|
22826
|
+
reason: `HarnessTrim reduced ${extracted.toolName} output (${result.reducer}):
|
|
22827
|
+
|
|
22828
|
+
${result.output}`
|
|
22829
|
+
});
|
|
22830
|
+
return {
|
|
22831
|
+
response,
|
|
22832
|
+
event: {
|
|
22833
|
+
tool: extracted.toolName,
|
|
22834
|
+
reducer: result.reducer,
|
|
22835
|
+
beforeChars: extracted.output.length,
|
|
22836
|
+
afterChars: result.output.length
|
|
22837
|
+
}
|
|
22838
|
+
};
|
|
22839
|
+
}
|
|
22840
|
+
function extractToolOutput(rawJson) {
|
|
22841
|
+
let payload;
|
|
22842
|
+
try {
|
|
22843
|
+
payload = JSON.parse(rawJson);
|
|
22844
|
+
} catch {
|
|
22845
|
+
return null;
|
|
22846
|
+
}
|
|
22847
|
+
if (typeof payload !== "object" || payload === null) return null;
|
|
22848
|
+
const p = payload;
|
|
22849
|
+
const output = extractOutputText(p.tool_response);
|
|
22850
|
+
return output === null ? null : { toolName: typeof p.tool_name === "string" ? p.tool_name : "unknown", output };
|
|
22851
|
+
}
|
|
22852
|
+
function extractOutputText(response) {
|
|
22853
|
+
if (typeof response === "string") return response;
|
|
22854
|
+
if (typeof response !== "object" || response === null) return null;
|
|
22855
|
+
const r = response;
|
|
22856
|
+
for (const key of ["stdout", "output", "content"]) {
|
|
22857
|
+
if (typeof r[key] === "string") return r[key];
|
|
22858
|
+
}
|
|
22859
|
+
return null;
|
|
22860
|
+
}
|
|
22861
|
+
|
|
22862
|
+
// ../adapter-codex/src/index.ts
|
|
22804
22863
|
var HARNESSTRIM_MARKER = "harnesstrim:begin";
|
|
22864
|
+
var CODEX_HOOK_COMMAND = "harnesstrim hook codex --metrics .harnesstrim/metrics.jsonl";
|
|
22865
|
+
var CODEX_HOOK_MATCHER = "^Bash$";
|
|
22805
22866
|
var REDUCE_INSTRUCTION_SNIPPET = `<!-- ${HARNESSTRIM_MARKER} -->
|
|
22806
22867
|
## Token economy (HarnessTrim)
|
|
22807
22868
|
|
|
@@ -22814,6 +22875,45 @@ This keeps failures, errors, assertions, and summaries while dropping passing-te
|
|
|
22814
22875
|
generated-file (lockfile/dist) diffs. Prefer the installed skills for output, review, and
|
|
22815
22876
|
scaffolding discipline.
|
|
22816
22877
|
<!-- harnesstrim:end -->`;
|
|
22878
|
+
function hasHarnessTrimHook(document) {
|
|
22879
|
+
const hooks = document.hooks;
|
|
22880
|
+
const post = hooks?.PostToolUse;
|
|
22881
|
+
if (!Array.isArray(post)) return false;
|
|
22882
|
+
return post.some(
|
|
22883
|
+
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some((hook) => typeof hook?.command === "string" && hook.command.includes("harnesstrim hook codex"))
|
|
22884
|
+
);
|
|
22885
|
+
}
|
|
22886
|
+
function planCodexHookInstall(input) {
|
|
22887
|
+
let document = {};
|
|
22888
|
+
let action;
|
|
22889
|
+
if (input.hooksJsonContent === null) {
|
|
22890
|
+
action = "create";
|
|
22891
|
+
} else {
|
|
22892
|
+
let parsed;
|
|
22893
|
+
try {
|
|
22894
|
+
parsed = JSON.parse(input.hooksJsonContent);
|
|
22895
|
+
} catch {
|
|
22896
|
+
throw new Error(".codex/hooks.json is not valid JSON; refusing to overwrite it.");
|
|
22897
|
+
}
|
|
22898
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
22899
|
+
throw new Error(".codex/hooks.json must contain a JSON object; refusing to overwrite it.");
|
|
22900
|
+
}
|
|
22901
|
+
document = parsed;
|
|
22902
|
+
action = hasHarnessTrimHook(document) ? "present" : "patch";
|
|
22903
|
+
}
|
|
22904
|
+
if (action === "present") {
|
|
22905
|
+
return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
|
|
22906
|
+
}
|
|
22907
|
+
const hooks = { ...document.hooks ?? {} };
|
|
22908
|
+
const post = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
|
|
22909
|
+
post.push({ matcher: CODEX_HOOK_MATCHER, hooks: [{ type: "command", command: CODEX_HOOK_COMMAND }] });
|
|
22910
|
+
hooks.PostToolUse = post;
|
|
22911
|
+
return {
|
|
22912
|
+
hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"),
|
|
22913
|
+
action,
|
|
22914
|
+
nextHooks: { ...document, hooks }
|
|
22915
|
+
};
|
|
22916
|
+
}
|
|
22817
22917
|
function planCodexInstall(input) {
|
|
22818
22918
|
const skillsDest = path3.join(input.projectDir, ".codex", "skills");
|
|
22819
22919
|
const existing = new Set(input.existingSkillNames);
|
|
@@ -22881,7 +22981,20 @@ function existingSkillNames(dest) {
|
|
|
22881
22981
|
}
|
|
22882
22982
|
|
|
22883
22983
|
// src/install-codex.ts
|
|
22884
|
-
function
|
|
22984
|
+
function readHooksJson(hooksPath) {
|
|
22985
|
+
try {
|
|
22986
|
+
return fs5.readFileSync(hooksPath, "utf8");
|
|
22987
|
+
} catch {
|
|
22988
|
+
return null;
|
|
22989
|
+
}
|
|
22990
|
+
}
|
|
22991
|
+
function applyHookPlan(plan, apply) {
|
|
22992
|
+
if (!apply || plan.action === "present") return false;
|
|
22993
|
+
fs5.mkdirSync(path6.dirname(plan.hooksFile), { recursive: true });
|
|
22994
|
+
fs5.writeFileSync(plan.hooksFile, JSON.stringify(plan.nextHooks, null, 2) + "\n");
|
|
22995
|
+
return true;
|
|
22996
|
+
}
|
|
22997
|
+
function runInstallCodex(dir, apply, hook = false) {
|
|
22885
22998
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
22886
22999
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
22887
23000
|
const skillsDest = path6.join(dir, ".codex", "skills");
|
|
@@ -22899,6 +23012,9 @@ function runInstallCodex(dir, apply) {
|
|
|
22899
23012
|
agentsMdContent,
|
|
22900
23013
|
existingSkillNames: existingSkillNames(skillsDest)
|
|
22901
23014
|
});
|
|
23015
|
+
const hooksPath = path6.join(dir, ".codex", "hooks.json");
|
|
23016
|
+
const hooksJsonContent = hook ? readHooksJson(hooksPath) : null;
|
|
23017
|
+
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent }) : null;
|
|
22902
23018
|
const copied = [];
|
|
22903
23019
|
let applied = false;
|
|
22904
23020
|
if (apply) {
|
|
@@ -22912,9 +23028,20 @@ function runInstallCodex(dir, apply) {
|
|
|
22912
23028
|
} else if (plan.instructionsAction === "append") {
|
|
22913
23029
|
fs5.appendFileSync(plan.instructionsFile, "\n\n" + plan.instructionsSnippet + "\n");
|
|
22914
23030
|
}
|
|
23031
|
+
if (hookPlan) applyHookPlan(hookPlan, true);
|
|
22915
23032
|
applied = true;
|
|
22916
23033
|
}
|
|
22917
|
-
return { plan, applied, copied };
|
|
23034
|
+
return { plan, hookPlan, applied, copied };
|
|
23035
|
+
}
|
|
23036
|
+
function runInstallCodexGlobalHook(codexHome, apply) {
|
|
23037
|
+
const hooksPath = path6.join(codexHome, "hooks.json");
|
|
23038
|
+
const hookPlan = planCodexHookInstall({
|
|
23039
|
+
// The planner expects the directory that contains .codex; for a user-level config
|
|
23040
|
+
// the Codex home is itself that directory, so add its parent and use a normal path.
|
|
23041
|
+
projectDir: path6.dirname(codexHome),
|
|
23042
|
+
hooksJsonContent: readHooksJson(hooksPath)
|
|
23043
|
+
});
|
|
23044
|
+
return { hookPlan, applied: applyHookPlan(hookPlan, apply) };
|
|
22918
23045
|
}
|
|
22919
23046
|
|
|
22920
23047
|
// src/install-claude.ts
|
|
@@ -22924,7 +23051,7 @@ import path8 from "node:path";
|
|
|
22924
23051
|
// ../adapter-claude/src/hook.ts
|
|
22925
23052
|
init_src();
|
|
22926
23053
|
function reduceClaudePayload(rawJson, minLength) {
|
|
22927
|
-
const extracted =
|
|
23054
|
+
const extracted = extractToolOutput2(rawJson);
|
|
22928
23055
|
if (extracted === null) return { response: "{}", event: null };
|
|
22929
23056
|
const result = reduceAuto(extracted.output, minLength);
|
|
22930
23057
|
if (!result.changed) return { response: "{}", event: null };
|
|
@@ -22944,7 +23071,7 @@ function reduceClaudePayload(rawJson, minLength) {
|
|
|
22944
23071
|
}
|
|
22945
23072
|
};
|
|
22946
23073
|
}
|
|
22947
|
-
function
|
|
23074
|
+
function extractToolOutput2(rawJson) {
|
|
22948
23075
|
let payload;
|
|
22949
23076
|
try {
|
|
22950
23077
|
payload = JSON.parse(rawJson);
|
|
@@ -22954,10 +23081,10 @@ function extractToolOutput(rawJson) {
|
|
|
22954
23081
|
if (typeof payload !== "object" || payload === null) return null;
|
|
22955
23082
|
const p = payload;
|
|
22956
23083
|
const toolName = typeof p.tool_name === "string" ? p.tool_name : "unknown";
|
|
22957
|
-
const output =
|
|
23084
|
+
const output = extractOutputText2(p);
|
|
22958
23085
|
return output === null ? null : { toolName, output };
|
|
22959
23086
|
}
|
|
22960
|
-
function
|
|
23087
|
+
function extractOutputText2(p) {
|
|
22961
23088
|
if (typeof p.tool_output === "string") return p.tool_output;
|
|
22962
23089
|
const resp = p.tool_response;
|
|
22963
23090
|
if (typeof resp === "string") return resp;
|
|
@@ -22974,7 +23101,7 @@ function extractOutputText(p) {
|
|
|
22974
23101
|
import path7 from "node:path";
|
|
22975
23102
|
var HOOK_COMMAND = "harnesstrim hook claude";
|
|
22976
23103
|
var HOOK_MATCHER = "Bash";
|
|
22977
|
-
function
|
|
23104
|
+
function hasHarnessTrimHook2(settings) {
|
|
22978
23105
|
const hooks = settings.hooks;
|
|
22979
23106
|
const post = hooks?.PostToolUse;
|
|
22980
23107
|
if (!Array.isArray(post)) return false;
|
|
@@ -23002,7 +23129,7 @@ function planClaudeInstall(input) {
|
|
|
23002
23129
|
} catch {
|
|
23003
23130
|
settings = {};
|
|
23004
23131
|
}
|
|
23005
|
-
action =
|
|
23132
|
+
action = hasHarnessTrimHook2(settings) ? "present" : "patch";
|
|
23006
23133
|
}
|
|
23007
23134
|
const nextSettings = action === "present" ? settings : addHook(settings);
|
|
23008
23135
|
return {
|
|
@@ -23327,12 +23454,42 @@ function renderCodexInstall(result, apply) {
|
|
|
23327
23454
|
lines.push("");
|
|
23328
23455
|
const instr = plan.instructionsAction === "present" ? "AGENTS.md already contains the HarnessTrim instruction (no change)." : plan.instructionsAction === "create" ? `AGENTS.md ${apply ? "created" : "would be created"} with the reduce-pipe instruction.` : `Reduce-pipe instruction ${apply ? "appended" : "would be appended"} to AGENTS.md.`;
|
|
23329
23456
|
lines.push(instr);
|
|
23457
|
+
if (result.hookPlan) {
|
|
23458
|
+
lines.push("");
|
|
23459
|
+
if (result.hookPlan.action === "present") {
|
|
23460
|
+
lines.push(`${result.hookPlan.hooksFile}: HarnessTrim Bash PostToolUse hook already present (no change).`);
|
|
23461
|
+
} else {
|
|
23462
|
+
lines.push(
|
|
23463
|
+
`${result.hookPlan.hooksFile}: experimental Bash PostToolUse hook ${apply ? result.hookPlan.action === "create" ? "created" : "added" : "would be added"}.`
|
|
23464
|
+
);
|
|
23465
|
+
lines.push("It reduces simple Bash output automatically and records JSONL telemetry in .harnesstrim/metrics.jsonl.");
|
|
23466
|
+
if (!apply) {
|
|
23467
|
+
lines.push("Resulting hooks.json:");
|
|
23468
|
+
lines.push(JSON.stringify(result.hookPlan.nextHooks, null, 2));
|
|
23469
|
+
}
|
|
23470
|
+
}
|
|
23471
|
+
}
|
|
23330
23472
|
if (!apply) {
|
|
23331
23473
|
lines.push("");
|
|
23332
23474
|
lines.push("Dry run \u2014 nothing written. Re-run with `--apply`.");
|
|
23333
23475
|
}
|
|
23334
23476
|
return lines.join("\n");
|
|
23335
23477
|
}
|
|
23478
|
+
function renderCodexGlobalHookInstall(result, apply) {
|
|
23479
|
+
const lines = [`${apply ? "Installed" : "Would install"} global Codex Bash PostToolUse hook`, ""];
|
|
23480
|
+
if (result.hookPlan.action === "present") {
|
|
23481
|
+
lines.push(`${result.hookPlan.hooksFile}: HarnessTrim hook already present (no change).`);
|
|
23482
|
+
} else {
|
|
23483
|
+
lines.push(`${result.hookPlan.hooksFile}: experimental Bash PostToolUse hook ${apply ? result.hookPlan.action === "create" ? "created" : "added" : "would be added"}.`);
|
|
23484
|
+
lines.push("It applies in trusted projects and writes telemetry to each project's .harnesstrim/metrics.jsonl.");
|
|
23485
|
+
if (!apply) {
|
|
23486
|
+
lines.push("Resulting hooks.json:");
|
|
23487
|
+
lines.push(JSON.stringify(result.hookPlan.nextHooks, null, 2));
|
|
23488
|
+
}
|
|
23489
|
+
}
|
|
23490
|
+
if (!apply) lines.push("", "Dry run \u2014 nothing written. Re-run with `--apply`.");
|
|
23491
|
+
return lines.join("\n");
|
|
23492
|
+
}
|
|
23336
23493
|
function renderClaudeInstall(result, apply) {
|
|
23337
23494
|
const { plan } = result;
|
|
23338
23495
|
const lines = [];
|
|
@@ -23444,8 +23601,10 @@ Usage:
|
|
|
23444
23601
|
harnesstrim install opencode [dir] Wire the adapter into opencode.json (dry-run)
|
|
23445
23602
|
--apply Actually write the change
|
|
23446
23603
|
--preset <name> Bake a policy preset's adapter config in
|
|
23447
|
-
harnesstrim install codex [dir] Install skills + AGENTS.md
|
|
23604
|
+
harnesstrim install codex [dir] Install skills + AGENTS.md reduction guidance (dry-run)
|
|
23448
23605
|
--apply Actually write the change
|
|
23606
|
+
--hook Also install the experimental Bash PostToolUse hook
|
|
23607
|
+
--global With --hook, install it once in ~/.codex (no project files)
|
|
23449
23608
|
harnesstrim install claude [dir] Install skills + PostToolUse reducer hook (dry-run)
|
|
23450
23609
|
--apply Actually write the change
|
|
23451
23610
|
harnesstrim install hermes [dir] Install Hermes plugin (dry-run)
|
|
@@ -23454,6 +23613,8 @@ Usage:
|
|
|
23454
23613
|
--apply Actually write the change
|
|
23455
23614
|
harnesstrim hook claude [--metrics <path>]
|
|
23456
23615
|
PostToolUse hook runtime; --metrics records a TrimEvent per reduction
|
|
23616
|
+
harnesstrim hook codex [--metrics <path>]
|
|
23617
|
+
PostToolUse runtime for Codex's experimental Bash hook
|
|
23457
23618
|
harnesstrim preset list List policy presets
|
|
23458
23619
|
harnesstrim preset show <name> Show a preset in detail
|
|
23459
23620
|
harnesstrim metrics [path] Summarize adapter telemetry (JSONL)
|
|
@@ -23477,7 +23638,9 @@ async function main(argv) {
|
|
|
23477
23638
|
stats: { type: "boolean" },
|
|
23478
23639
|
"min-length": { type: "string" },
|
|
23479
23640
|
log: { type: "string" },
|
|
23480
|
-
metrics: { type: "string" }
|
|
23641
|
+
metrics: { type: "string" },
|
|
23642
|
+
hook: { type: "boolean" },
|
|
23643
|
+
global: { type: "boolean" }
|
|
23481
23644
|
}
|
|
23482
23645
|
});
|
|
23483
23646
|
const [command, ...rest] = positionals;
|
|
@@ -23501,7 +23664,15 @@ async function main(argv) {
|
|
|
23501
23664
|
return 0;
|
|
23502
23665
|
}
|
|
23503
23666
|
if (target === "codex") {
|
|
23504
|
-
|
|
23667
|
+
if (values.global === true) {
|
|
23668
|
+
if (values.hook !== true) {
|
|
23669
|
+
console.error("`harnesstrim install codex --global` requires `--hook`.");
|
|
23670
|
+
return 1;
|
|
23671
|
+
}
|
|
23672
|
+
console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path14.join(os2.homedir(), ".codex"), apply), apply));
|
|
23673
|
+
return 0;
|
|
23674
|
+
}
|
|
23675
|
+
console.log(renderCodexInstall(runInstallCodex(dir, apply, values.hook === true), apply));
|
|
23505
23676
|
return 0;
|
|
23506
23677
|
}
|
|
23507
23678
|
if (target === "claude") {
|
|
@@ -23521,12 +23692,12 @@ async function main(argv) {
|
|
|
23521
23692
|
}
|
|
23522
23693
|
case "hook": {
|
|
23523
23694
|
const which = rest[0];
|
|
23524
|
-
if (which !== "claude") {
|
|
23525
|
-
console.error(`Unknown hook target: ${which ?? "(none)"}. Supported: claude.`);
|
|
23695
|
+
if (which !== "claude" && which !== "codex") {
|
|
23696
|
+
console.error(`Unknown hook target: ${which ?? "(none)"}. Supported: claude, codex.`);
|
|
23526
23697
|
return 1;
|
|
23527
23698
|
}
|
|
23528
23699
|
const input = await readStdin();
|
|
23529
|
-
const { response, event } = reduceClaudePayload(input);
|
|
23700
|
+
const { response, event } = which === "claude" ? reduceClaudePayload(input) : reduceCodexPayload(input);
|
|
23530
23701
|
process.stdout.write(response);
|
|
23531
23702
|
if (values.metrics && event) {
|
|
23532
23703
|
try {
|
|
@@ -23534,7 +23705,7 @@ async function main(argv) {
|
|
|
23534
23705
|
fs10.mkdirSync(path14.dirname(p), { recursive: true });
|
|
23535
23706
|
fs10.appendFileSync(
|
|
23536
23707
|
p,
|
|
23537
|
-
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), harness:
|
|
23708
|
+
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), harness: which, ...event }) + "\n"
|
|
23538
23709
|
);
|
|
23539
23710
|
} catch {
|
|
23540
23711
|
}
|