haansi 0.1.57 → 0.1.58

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/haansi.js +167 -24
  2. package/package.json +1 -1
package/dist/haansi.js CHANGED
@@ -95,7 +95,7 @@ var require_package = __commonJS({
95
95
  "package.json"(exports2, module2) {
96
96
  module2.exports = {
97
97
  name: "haansi",
98
- version: "0.1.57",
98
+ version: "0.1.58",
99
99
  description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
100
100
  bin: {
101
101
  haansi: "./dist/haansi.js"
@@ -19246,7 +19246,15 @@ var init_exclusions = __esm({
19246
19246
  arm: ["operational", "Closed CHECK vocabulary (baseline, haansi)."],
19247
19247
  agent: ["operational", "The agent CLI's name, e.g. claude-code."],
19248
19248
  model: ["operational", "The model name the replay ran with."],
19249
- run_ref: ["not-personal", "A link to the customer's CI run."]
19249
+ run_ref: ["not-personal", "A link to the customer's CI run."],
19250
+ haansi_used: [
19251
+ "operational",
19252
+ "Closed CHECK vocabulary (used, partly, not_used)."
19253
+ ],
19254
+ haansi_judge_tag: [
19255
+ "not-personal",
19256
+ "The prompt version that judged it (replay-use-judge-vN)."
19257
+ ]
19250
19258
  }),
19251
19259
  ...exchangeExclusions(INTEL, "replay_tasks", {
19252
19260
  process_id: [
@@ -77508,7 +77516,7 @@ var require_view = __commonJS({
77508
77516
  var dirname5 = path2.dirname;
77509
77517
  var basename4 = path2.basename;
77510
77518
  var extname = path2.extname;
77511
- var join17 = path2.join;
77519
+ var join18 = path2.join;
77512
77520
  var resolve5 = path2.resolve;
77513
77521
  module2.exports = View;
77514
77522
  function View(name, options) {
@@ -77570,12 +77578,12 @@ var require_view = __commonJS({
77570
77578
  };
77571
77579
  View.prototype.resolve = function resolve6(dir, file2) {
77572
77580
  var ext = this.ext;
77573
- var path3 = join17(dir, file2);
77581
+ var path3 = join18(dir, file2);
77574
77582
  var stat = tryStat(path3);
77575
77583
  if (stat && stat.isFile()) {
77576
77584
  return path3;
77577
77585
  }
77578
- path3 = join17(dir, basename4(file2, ext), "index" + ext);
77586
+ path3 = join18(dir, basename4(file2, ext), "index" + ext);
77579
77587
  stat = tryStat(path3);
77580
77588
  if (stat && stat.isFile()) {
77581
77589
  return path3;
@@ -81220,7 +81228,7 @@ var require_send = __commonJS({
81220
81228
  var Stream = require("stream");
81221
81229
  var util2 = require("util");
81222
81230
  var extname = path2.extname;
81223
- var join17 = path2.join;
81231
+ var join18 = path2.join;
81224
81232
  var normalize = path2.normalize;
81225
81233
  var resolve5 = path2.resolve;
81226
81234
  var sep2 = path2.sep;
@@ -81392,7 +81400,7 @@ var require_send = __commonJS({
81392
81400
  return res;
81393
81401
  }
81394
81402
  parts = path3.split(sep2);
81395
- path3 = normalize(join17(root, path3));
81403
+ path3 = normalize(join18(root, path3));
81396
81404
  } else {
81397
81405
  if (UP_PATH_REGEXP.test(path3)) {
81398
81406
  debug('malicious path "%s"', path3);
@@ -81525,7 +81533,7 @@ var require_send = __commonJS({
81525
81533
  if (err2) return self.onStatError(err2);
81526
81534
  return self.error(404);
81527
81535
  }
81528
- var p = join17(path3, self._index[i]);
81536
+ var p = join18(path3, self._index[i]);
81529
81537
  debug('stat "%s"', p);
81530
81538
  fs.stat(p, function(err3, stat) {
81531
81539
  if (err3) return next(err3);
@@ -85603,6 +85611,111 @@ them all.`;
85603
85611
  }
85604
85612
  });
85605
85613
 
85614
+ // src/commands/replay-judge.ts
85615
+ function renderTemplate(template, vars) {
85616
+ return template.replace(
85617
+ /\{\{(\w+)\}\}/g,
85618
+ (all, key) => key in vars ? vars[key] : all
85619
+ );
85620
+ }
85621
+ async function changeOf(deps, dir, baseSha) {
85622
+ await deps.git(["add", "--all", "--intent-to-add"], dir);
85623
+ const diff = await deps.git(
85624
+ ["diff", "--no-color", baseSha, "--", ".", ":(exclude).haansi-replay"],
85625
+ dir
85626
+ );
85627
+ return diff.code === 0 ? clip(diff.output, MAX_CHANGE_CHARS) : "";
85628
+ }
85629
+ function parseJudgement(text, returnedIds) {
85630
+ const match = /\{[\s\S]*\}/.exec(text);
85631
+ if (!match) return null;
85632
+ let v;
85633
+ try {
85634
+ v = JSON.parse(match[0]);
85635
+ } catch {
85636
+ return null;
85637
+ }
85638
+ if (!VERDICTS.includes(v.verdict)) return null;
85639
+ const items = Array.isArray(v.used_items) ? new Set(
85640
+ v.used_items.filter(
85641
+ (id) => typeof id === "string" && returnedIds.has(id)
85642
+ )
85643
+ ) : /* @__PURE__ */ new Set();
85644
+ const reason = typeof v.reason === "string" && v.reason.trim() ? v.reason.trim().slice(0, MAX_REASON) : null;
85645
+ return { used: v.verdict, reason, usedItems: items.size };
85646
+ }
85647
+ async function judgeUse(deps, input) {
85648
+ const returnedIds = new Set(input.calls.flatMap((c) => c.returnedIds));
85649
+ if (input.calls.length === 0)
85650
+ return {
85651
+ used: "not_used",
85652
+ reason: "The agent did not call Haansi.",
85653
+ usedItems: 0,
85654
+ judgeTag: null
85655
+ };
85656
+ if (returnedIds.size === 0)
85657
+ return {
85658
+ used: "not_used",
85659
+ reason: "Haansi returned nothing.",
85660
+ usedItems: 0,
85661
+ judgeTag: null
85662
+ };
85663
+ if (!input.prompt)
85664
+ return { used: null, reason: null, usedItems: null, judgeTag: null };
85665
+ const tag = input.prompt.edited ? `${input.prompt.tag}+edited` : input.prompt.tag;
85666
+ const dir = (0, import_node_path10.join)(deps.workDir(), `${input.taskId}-judge`);
85667
+ const verdictFile = (0, import_node_path10.join)(dir, "verdict.json");
85668
+ const promptFile = (0, import_node_path10.join)(dir, "prompt.md");
85669
+ const configFile = (0, import_node_path10.join)(dir, "mcp.json");
85670
+ const returned = input.calls.map((c) => c.returnedText ?? "").filter(Boolean).join("\n\n");
85671
+ const prompt2 = [
85672
+ input.prompt.system,
85673
+ "",
85674
+ renderTemplate(input.prompt.user, {
85675
+ task: input.card,
85676
+ returned: clip(returned || "(only ids came back)", MAX_RETURNED_CHARS),
85677
+ change: input.change || "(no change)",
85678
+ verdict_file: verdictFile
85679
+ })
85680
+ ].join("\n");
85681
+ try {
85682
+ deps.writeFile(promptFile, prompt2);
85683
+ deps.writeFile(configFile, JSON.stringify({ mcpServers: {} }));
85684
+ const ran = await deps.shell(
85685
+ input.agentCmd,
85686
+ dir,
85687
+ {
85688
+ HAANSI_REPLAY_PROMPT: prompt2,
85689
+ HAANSI_REPLAY_PROMPT_FILE: promptFile,
85690
+ HAANSI_REPLAY_MCP_CONFIG: configFile,
85691
+ HAANSI_REPLAY_VERDICT_FILE: verdictFile
85692
+ },
85693
+ input.timeoutMs
85694
+ );
85695
+ const judged = parseJudgement(deps.readFile(verdictFile) ?? "", returnedIds) ?? parseJudgement(ran.output, returnedIds);
85696
+ if (!judged) {
85697
+ deps.log(`\u2026 ${input.label} [haansi]: the judge gave no verdict`);
85698
+ return { used: null, reason: null, usedItems: null, judgeTag: tag };
85699
+ }
85700
+ return { ...judged, judgeTag: tag };
85701
+ } finally {
85702
+ deps.removeDir(dir);
85703
+ }
85704
+ }
85705
+ var import_node_path10, MAX_CHANGE_CHARS, MAX_RETURNED_CHARS, MAX_REASON, VERDICTS, clip;
85706
+ var init_replay_judge = __esm({
85707
+ "src/commands/replay-judge.ts"() {
85708
+ "use strict";
85709
+ import_node_path10 = require("node:path");
85710
+ MAX_CHANGE_CHARS = 6e4;
85711
+ MAX_RETURNED_CHARS = 6e4;
85712
+ MAX_REASON = 300;
85713
+ VERDICTS = ["used", "partly", "not_used"];
85714
+ clip = (text, max2) => text.length > max2 ? `${text.slice(0, max2)}
85715
+ \u2026 (trimmed)` : text;
85716
+ }
85717
+ });
85718
+
85606
85719
  // src/commands/replay-tasks.ts
85607
85720
  var replay_tasks_exports = {};
85608
85721
  __export(replay_tasks_exports, {
@@ -85721,6 +85834,12 @@ async function runStoredReplays(options, deps) {
85721
85834
  const notValid = set2.tasks.length - valid.length;
85722
85835
  const arms = options.arms;
85723
85836
  const chosen = options.limit ? valid.slice(0, options.limit) : valid;
85837
+ let judgePrompt;
85838
+ const promptForJudge = async () => {
85839
+ if (judgePrompt === void 0)
85840
+ judgePrompt = await deps.getJudgePrompt().catch(() => null);
85841
+ return judgePrompt;
85842
+ };
85724
85843
  const results = [];
85725
85844
  for (const task of chosen) {
85726
85845
  const label = task.name ?? task.taskKey;
@@ -85738,6 +85857,7 @@ async function runStoredReplays(options, deps) {
85738
85857
  };
85739
85858
  for (const arm of arms) {
85740
85859
  let changed = [];
85860
+ let change = "";
85741
85861
  try {
85742
85862
  const session = arm === "haansi" ? {
85743
85863
  ...await deps.openSession(task.taskId),
@@ -85755,11 +85875,29 @@ async function runStoredReplays(options, deps) {
85755
85875
  (feedback) => cardPrompt(task, feedback, arm),
85756
85876
  async (dir) => {
85757
85877
  changed = await changedFiles(deps, dir, task.baseSha);
85878
+ if (arm === "haansi")
85879
+ change = await changeOf(deps, dir, task.baseSha);
85758
85880
  },
85759
85881
  { HAANSI_REPLAY_MCP_CONFIG: configPath }
85760
85882
  );
85761
85883
  deps.removeFile(configPath);
85762
- const haansi = session ? haansiUseOf((await deps.getCalls(session.sessionId)).calls) : null;
85884
+ let haansi = null;
85885
+ if (session) {
85886
+ const { calls } = await deps.getCalls(session.sessionId);
85887
+ haansi = {
85888
+ ...haansiUseOf(calls),
85889
+ ...await judgeUse(deps, {
85890
+ label,
85891
+ taskId: task.taskId,
85892
+ agentCmd: options.agentCmd,
85893
+ timeoutMs: options.timeoutMs,
85894
+ card: cardPrompt(task, null),
85895
+ calls,
85896
+ change,
85897
+ prompt: await promptForJudge()
85898
+ })
85899
+ };
85900
+ }
85763
85901
  const overlap = overlapOf(changed, task);
85764
85902
  results.push({
85765
85903
  taskId: task.taskId,
@@ -85808,13 +85946,17 @@ function haansiUseOf(calls) {
85808
85946
  };
85809
85947
  }
85810
85948
  function haansiText(use) {
85811
- return use.calls === 0 ? "did not call Haansi" : `called Haansi ${use.calls}\xD7, ${use.returned} item${use.returned === 1 ? "" : "s"} returned`;
85949
+ if (use.calls === 0) return "did not call Haansi";
85950
+ const base = `called Haansi ${use.calls}\xD7, ${use.returned} item${use.returned === 1 ? "" : "s"} returned`;
85951
+ if (!use.used) return base;
85952
+ return use.used === "not_used" ? `${base}, not used` : `${base}, ${use.used === "partly" ? "partly " : ""}used${use.usedItems ? ` (${use.usedItems})` : ""}`;
85812
85953
  }
85813
85954
  var FEEDBACK_HEADER, REPORT_CHUNK2, HAANSI_LINE, share, pct;
85814
85955
  var init_replay_tasks = __esm({
85815
85956
  "src/commands/replay-tasks.ts"() {
85816
85957
  "use strict";
85817
85958
  init_replay_calibrate();
85959
+ init_replay_judge();
85818
85960
  init_replay();
85819
85961
  FEEDBACK_HEADER = "Your previous attempt did not pass the tests. Their output ends with:";
85820
85962
  REPORT_CHUNK2 = 200;
@@ -85994,7 +86136,7 @@ function cleanCheckout(deps, dir, task) {
85994
86136
  }
85995
86137
  async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect, agentEnv = {}) {
85996
86138
  const started = deps.now();
85997
- const dir = (0, import_node_path10.join)(
86139
+ const dir = (0, import_node_path11.join)(
85998
86140
  deps.workDir(),
85999
86141
  `${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
86000
86142
  );
@@ -86020,7 +86162,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86020
86162
  }
86021
86163
  deps.log(`\u2026 ${label}: set up in ${since(at)}`);
86022
86164
  }
86023
- const promptFile = (0, import_node_path10.join)(dir, ".haansi-replay", "prompt.md");
86165
+ const promptFile = (0, import_node_path11.join)(dir, ".haansi-replay", "prompt.md");
86024
86166
  let mcpConfigFile = agentEnv.HAANSI_REPLAY_MCP_CONFIG;
86025
86167
  if (!mcpConfigFile) {
86026
86168
  mcpConfigFile = `${dir}.mcp.json`;
@@ -86028,7 +86170,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86028
86170
  }
86029
86171
  if (arm === "haansi" && playbook)
86030
86172
  deps.writeFile(
86031
- (0, import_node_path10.join)(dir, ".haansi-replay", "playbook.md"),
86173
+ (0, import_node_path11.join)(dir, ".haansi-replay", "playbook.md"),
86032
86174
  playbook.skillMd
86033
86175
  );
86034
86176
  let feedback = null;
@@ -86050,11 +86192,11 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86050
86192
  },
86051
86193
  options.timeoutMs
86052
86194
  );
86053
- const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path10.join)(dir, f))]));
86195
+ const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path11.join)(dir, f))]));
86054
86196
  for (const f of files) {
86055
86197
  const content = await deps.showFile(task.targetSha, f);
86056
- if (content === null) deps.removeFile((0, import_node_path10.join)(dir, f));
86057
- else deps.writeFile((0, import_node_path10.join)(dir, f), content);
86198
+ if (content === null) deps.removeFile((0, import_node_path11.join)(dir, f));
86199
+ else deps.writeFile((0, import_node_path11.join)(dir, f), content);
86058
86200
  }
86059
86201
  const testAt = deps.now();
86060
86202
  deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
@@ -86084,8 +86226,8 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86084
86226
  };
86085
86227
  }
86086
86228
  for (const [file2, content] of own) {
86087
- if (content === null) deps.removeFile((0, import_node_path10.join)(dir, file2));
86088
- else deps.writeFile((0, import_node_path10.join)(dir, file2), content);
86229
+ if (content === null) deps.removeFile((0, import_node_path11.join)(dir, file2));
86230
+ else deps.writeFile((0, import_node_path11.join)(dir, file2), content);
86089
86231
  }
86090
86232
  feedback = test.output.slice(-FEEDBACK_CHARS);
86091
86233
  }
@@ -86235,7 +86377,7 @@ function localDeps(work) {
86235
86377
  repoDir: process.cwd(),
86236
86378
  workDir: () => work,
86237
86379
  writeFile: (path2, content) => {
86238
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(path2), { recursive: true });
86380
+ (0, import_node_fs11.mkdirSync)((0, import_node_path11.dirname)(path2), { recursive: true });
86239
86381
  (0, import_node_fs11.writeFileSync)(path2, content);
86240
86382
  },
86241
86383
  readFile: (path2) => (0, import_node_fs11.existsSync)(path2) ? (0, import_node_fs11.readFileSync)(path2, "utf-8") : null,
@@ -86264,7 +86406,7 @@ async function replay(argv) {
86264
86406
  await replayStored(options, token, apiUrl2);
86265
86407
  return;
86266
86408
  }
86267
- const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86409
+ const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86268
86410
  try {
86269
86411
  const { results, skipped, recorded } = await runReplays(options, {
86270
86412
  getTasks: async (familyId, cutoff) => (await apiGet3(
@@ -86301,7 +86443,7 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
86301
86443
  async function replayStored(options, token, apiUrl2) {
86302
86444
  const { runStoredReplays: runStoredReplays2 } = await Promise.resolve().then(() => (init_replay_tasks(), replay_tasks_exports));
86303
86445
  const { cliVersion: cliVersion2 } = await Promise.resolve().then(() => (init_replay_calibrate(), replay_calibrate_exports));
86304
- const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86446
+ const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86305
86447
  try {
86306
86448
  const { results, notValid, recorded } = await runStoredReplays2(options, {
86307
86449
  ...localDeps(work),
@@ -86331,10 +86473,11 @@ async function replayStored(options, token, apiUrl2) {
86331
86473
  apiUrl2
86332
86474
  ),
86333
86475
  getCalls: (sessionId) => apiGet3(
86334
- `/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls`,
86476
+ `/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls?include=text`,
86335
86477
  token,
86336
86478
  apiUrl2
86337
86479
  ),
86480
+ getJudgePrompt: () => apiGet3("/memory/replays/prompts/use-judge", token, apiUrl2),
86338
86481
  apiUrl: apiUrl2,
86339
86482
  token
86340
86483
  });
@@ -86356,14 +86499,14 @@ Baseline: ${arm("baseline")} passed \xB7 With Haansi: ${arm("haansi")} passed \x
86356
86499
  (0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
86357
86500
  }
86358
86501
  }
86359
- var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path10, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
86502
+ var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path11, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
86360
86503
  var init_replay = __esm({
86361
86504
  "src/commands/replay.ts"() {
86362
86505
  "use strict";
86363
86506
  import_node_child_process4 = require("node:child_process");
86364
86507
  import_node_fs11 = require("node:fs");
86365
86508
  import_node_os10 = require("node:os");
86366
- import_node_path10 = require("node:path");
86509
+ import_node_path11 = require("node:path");
86367
86510
  init_paths();
86368
86511
  init_api2();
86369
86512
  SHA = /^[0-9a-f]{7,40}$/i;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haansi",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
5
5
  "bin": {
6
6
  "haansi": "./dist/haansi.js"