haansi 0.1.57 → 0.1.59

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 +182 -32
  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.59",
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, {
@@ -85647,14 +85760,20 @@ function mcpConfig(arm, session) {
85647
85760
  HAANSI_REPLAY_SESSION: session.sessionId
85648
85761
  }
85649
85762
  },
85650
- "haansi-skills": {
85651
- type: "http",
85652
- url: `${base}/api/v1/memory/skills/mcp`,
85653
- headers: {
85654
- Authorization: `Bearer ${session.token}`,
85655
- "X-Haansi-Replay-Session": session.sessionId
85656
- }
85657
- }
85763
+ ...Object.fromEntries(
85764
+ // The same servers `haansi setup-mcp` gives a coding agent.
85765
+ HTTP_SERVERS.map(({ name, path: path2 }) => [
85766
+ name,
85767
+ {
85768
+ type: "http",
85769
+ url: `${base}${path2}`,
85770
+ headers: {
85771
+ Authorization: `Bearer ${session.token}`,
85772
+ "X-Haansi-Replay-Session": session.sessionId
85773
+ }
85774
+ }
85775
+ ])
85776
+ )
85658
85777
  }
85659
85778
  },
85660
85779
  null,
@@ -85721,6 +85840,12 @@ async function runStoredReplays(options, deps) {
85721
85840
  const notValid = set2.tasks.length - valid.length;
85722
85841
  const arms = options.arms;
85723
85842
  const chosen = options.limit ? valid.slice(0, options.limit) : valid;
85843
+ let judgePrompt;
85844
+ const promptForJudge = async () => {
85845
+ if (judgePrompt === void 0)
85846
+ judgePrompt = await deps.getJudgePrompt().catch(() => null);
85847
+ return judgePrompt;
85848
+ };
85724
85849
  const results = [];
85725
85850
  for (const task of chosen) {
85726
85851
  const label = task.name ?? task.taskKey;
@@ -85738,6 +85863,7 @@ async function runStoredReplays(options, deps) {
85738
85863
  };
85739
85864
  for (const arm of arms) {
85740
85865
  let changed = [];
85866
+ let change = "";
85741
85867
  try {
85742
85868
  const session = arm === "haansi" ? {
85743
85869
  ...await deps.openSession(task.taskId),
@@ -85755,11 +85881,29 @@ async function runStoredReplays(options, deps) {
85755
85881
  (feedback) => cardPrompt(task, feedback, arm),
85756
85882
  async (dir) => {
85757
85883
  changed = await changedFiles(deps, dir, task.baseSha);
85884
+ if (arm === "haansi")
85885
+ change = await changeOf(deps, dir, task.baseSha);
85758
85886
  },
85759
85887
  { HAANSI_REPLAY_MCP_CONFIG: configPath }
85760
85888
  );
85761
85889
  deps.removeFile(configPath);
85762
- const haansi = session ? haansiUseOf((await deps.getCalls(session.sessionId)).calls) : null;
85890
+ let haansi = null;
85891
+ if (session) {
85892
+ const { calls } = await deps.getCalls(session.sessionId);
85893
+ haansi = {
85894
+ ...haansiUseOf(calls),
85895
+ ...await judgeUse(deps, {
85896
+ label,
85897
+ taskId: task.taskId,
85898
+ agentCmd: options.agentCmd,
85899
+ timeoutMs: options.timeoutMs,
85900
+ card: cardPrompt(task, null),
85901
+ calls,
85902
+ change,
85903
+ prompt: await promptForJudge()
85904
+ })
85905
+ };
85906
+ }
85763
85907
  const overlap = overlapOf(changed, task);
85764
85908
  results.push({
85765
85909
  taskId: task.taskId,
@@ -85808,13 +85952,18 @@ function haansiUseOf(calls) {
85808
85952
  };
85809
85953
  }
85810
85954
  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`;
85955
+ if (use.calls === 0) return "did not call Haansi";
85956
+ const base = `called Haansi ${use.calls}\xD7, ${use.returned} item${use.returned === 1 ? "" : "s"} returned`;
85957
+ if (!use.used) return base;
85958
+ return use.used === "not_used" ? `${base}, not used` : `${base}, ${use.used === "partly" ? "partly " : ""}used${use.usedItems ? ` (${use.usedItems})` : ""}`;
85812
85959
  }
85813
85960
  var FEEDBACK_HEADER, REPORT_CHUNK2, HAANSI_LINE, share, pct;
85814
85961
  var init_replay_tasks = __esm({
85815
85962
  "src/commands/replay-tasks.ts"() {
85816
85963
  "use strict";
85964
+ init_setup_mcp();
85817
85965
  init_replay_calibrate();
85966
+ init_replay_judge();
85818
85967
  init_replay();
85819
85968
  FEEDBACK_HEADER = "Your previous attempt did not pass the tests. Their output ends with:";
85820
85969
  REPORT_CHUNK2 = 200;
@@ -85994,7 +86143,7 @@ function cleanCheckout(deps, dir, task) {
85994
86143
  }
85995
86144
  async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect, agentEnv = {}) {
85996
86145
  const started = deps.now();
85997
- const dir = (0, import_node_path10.join)(
86146
+ const dir = (0, import_node_path11.join)(
85998
86147
  deps.workDir(),
85999
86148
  `${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
86000
86149
  );
@@ -86020,7 +86169,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86020
86169
  }
86021
86170
  deps.log(`\u2026 ${label}: set up in ${since(at)}`);
86022
86171
  }
86023
- const promptFile = (0, import_node_path10.join)(dir, ".haansi-replay", "prompt.md");
86172
+ const promptFile = (0, import_node_path11.join)(dir, ".haansi-replay", "prompt.md");
86024
86173
  let mcpConfigFile = agentEnv.HAANSI_REPLAY_MCP_CONFIG;
86025
86174
  if (!mcpConfigFile) {
86026
86175
  mcpConfigFile = `${dir}.mcp.json`;
@@ -86028,7 +86177,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86028
86177
  }
86029
86178
  if (arm === "haansi" && playbook)
86030
86179
  deps.writeFile(
86031
- (0, import_node_path10.join)(dir, ".haansi-replay", "playbook.md"),
86180
+ (0, import_node_path11.join)(dir, ".haansi-replay", "playbook.md"),
86032
86181
  playbook.skillMd
86033
86182
  );
86034
86183
  let feedback = null;
@@ -86050,11 +86199,11 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86050
86199
  },
86051
86200
  options.timeoutMs
86052
86201
  );
86053
- const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path10.join)(dir, f))]));
86202
+ const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path11.join)(dir, f))]));
86054
86203
  for (const f of files) {
86055
86204
  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);
86205
+ if (content === null) deps.removeFile((0, import_node_path11.join)(dir, f));
86206
+ else deps.writeFile((0, import_node_path11.join)(dir, f), content);
86058
86207
  }
86059
86208
  const testAt = deps.now();
86060
86209
  deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
@@ -86084,8 +86233,8 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86084
86233
  };
86085
86234
  }
86086
86235
  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);
86236
+ if (content === null) deps.removeFile((0, import_node_path11.join)(dir, file2));
86237
+ else deps.writeFile((0, import_node_path11.join)(dir, file2), content);
86089
86238
  }
86090
86239
  feedback = test.output.slice(-FEEDBACK_CHARS);
86091
86240
  }
@@ -86235,7 +86384,7 @@ function localDeps(work) {
86235
86384
  repoDir: process.cwd(),
86236
86385
  workDir: () => work,
86237
86386
  writeFile: (path2, content) => {
86238
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(path2), { recursive: true });
86387
+ (0, import_node_fs11.mkdirSync)((0, import_node_path11.dirname)(path2), { recursive: true });
86239
86388
  (0, import_node_fs11.writeFileSync)(path2, content);
86240
86389
  },
86241
86390
  readFile: (path2) => (0, import_node_fs11.existsSync)(path2) ? (0, import_node_fs11.readFileSync)(path2, "utf-8") : null,
@@ -86264,7 +86413,7 @@ async function replay(argv) {
86264
86413
  await replayStored(options, token, apiUrl2);
86265
86414
  return;
86266
86415
  }
86267
- const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86416
+ const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86268
86417
  try {
86269
86418
  const { results, skipped, recorded } = await runReplays(options, {
86270
86419
  getTasks: async (familyId, cutoff) => (await apiGet3(
@@ -86301,7 +86450,7 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
86301
86450
  async function replayStored(options, token, apiUrl2) {
86302
86451
  const { runStoredReplays: runStoredReplays2 } = await Promise.resolve().then(() => (init_replay_tasks(), replay_tasks_exports));
86303
86452
  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-"));
86453
+ const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
86305
86454
  try {
86306
86455
  const { results, notValid, recorded } = await runStoredReplays2(options, {
86307
86456
  ...localDeps(work),
@@ -86331,10 +86480,11 @@ async function replayStored(options, token, apiUrl2) {
86331
86480
  apiUrl2
86332
86481
  ),
86333
86482
  getCalls: (sessionId) => apiGet3(
86334
- `/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls`,
86483
+ `/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls?include=text`,
86335
86484
  token,
86336
86485
  apiUrl2
86337
86486
  ),
86487
+ getJudgePrompt: () => apiGet3("/memory/replays/prompts/use-judge", token, apiUrl2),
86338
86488
  apiUrl: apiUrl2,
86339
86489
  token
86340
86490
  });
@@ -86356,14 +86506,14 @@ Baseline: ${arm("baseline")} passed \xB7 With Haansi: ${arm("haansi")} passed \x
86356
86506
  (0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
86357
86507
  }
86358
86508
  }
86359
- var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path10, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
86509
+ var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path11, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
86360
86510
  var init_replay = __esm({
86361
86511
  "src/commands/replay.ts"() {
86362
86512
  "use strict";
86363
86513
  import_node_child_process4 = require("node:child_process");
86364
86514
  import_node_fs11 = require("node:fs");
86365
86515
  import_node_os10 = require("node:os");
86366
- import_node_path10 = require("node:path");
86516
+ import_node_path11 = require("node:path");
86367
86517
  init_paths();
86368
86518
  init_api2();
86369
86519
  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.59",
4
4
  "description": "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
5
5
  "bin": {
6
6
  "haansi": "./dist/haansi.js"