haansi 0.1.56 → 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 +305 -45
  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.56",
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"
@@ -19220,6 +19220,24 @@ var init_exclusions = __esm({
19220
19220
  model: ["operational", "The model name the replay ran with."],
19221
19221
  run_ref: ["not-personal", "A link to the customer's CI run."]
19222
19222
  }),
19223
+ ...exchangeExclusions(INTEL, "replay_sessions", {
19224
+ excluded_process_ids: [
19225
+ "join-key",
19226
+ "process_registry.process_id of the replayed workstream, matched by equality."
19227
+ ],
19228
+ excluded_family_ids: [
19229
+ "join-key",
19230
+ "problem_families ids holding the replayed workstream, matched by equality."
19231
+ ]
19232
+ }),
19233
+ ...exchangeExclusions(INTEL, "replay_session_calls", {
19234
+ server: ["operational", "The MCP server's name, e.g. haansi-skills."],
19235
+ tool: ["operational", "The MCP tool's name, e.g. search_skills."],
19236
+ returned_ids: [
19237
+ "join-key",
19238
+ "Ids of the solutions, skills and revisions a tool returned."
19239
+ ]
19240
+ }),
19223
19241
  ...exchangeExclusions(INTEL, "replay_task_results", {
19224
19242
  inputs_fingerprint: [
19225
19243
  "derived",
@@ -19228,7 +19246,15 @@ var init_exclusions = __esm({
19228
19246
  arm: ["operational", "Closed CHECK vocabulary (baseline, haansi)."],
19229
19247
  agent: ["operational", "The agent CLI's name, e.g. claude-code."],
19230
19248
  model: ["operational", "The model name the replay ran with."],
19231
- 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
+ ]
19232
19258
  }),
19233
19259
  ...exchangeExclusions(INTEL, "replay_tasks", {
19234
19260
  process_id: [
@@ -19315,7 +19341,11 @@ var init_exclusions = __esm({
19315
19341
  "The CI's setup and test commands an org admin or CI owner typed, runtime versions, the runner image and the CLI version."
19316
19342
  ],
19317
19343
  calibration_runs: ["operational", "Exit codes of the test runs."],
19318
- calibration_run_ref: ["not-personal", "A link to the customer's CI run."]
19344
+ calibration_run_ref: ["not-personal", "A link to the customer's CI run."],
19345
+ review_state: [
19346
+ "operational",
19347
+ "Closed CHECK vocabulary (approved, excluded)."
19348
+ ]
19319
19349
  }),
19320
19350
  ...exchangeExclusions(INTEL, "problem_family_members", {
19321
19351
  process_id: [
@@ -26583,6 +26613,12 @@ var init_table_registry = __esm({
26583
26613
  entry("intelligence", INTELLIGENCE_DB, "replay_task_results", "direct", {
26584
26614
  scopingColumn: "organization_id"
26585
26615
  }),
26616
+ entry("intelligence", INTELLIGENCE_DB, "replay_sessions", "direct", {
26617
+ scopingColumn: "organization_id"
26618
+ }),
26619
+ entry("intelligence", INTELLIGENCE_DB, "replay_session_calls", "direct", {
26620
+ scopingColumn: "organization_id"
26621
+ }),
26586
26622
  // A person's revert verdicts, the checks that validate a change and the
26587
26623
  // replay commands, per repository (outcome evidence v2 plan).
26588
26624
  entry("intelligence", INTELLIGENCE_DB, "revert_labels", "direct", {
@@ -77480,7 +77516,7 @@ var require_view = __commonJS({
77480
77516
  var dirname5 = path2.dirname;
77481
77517
  var basename4 = path2.basename;
77482
77518
  var extname = path2.extname;
77483
- var join17 = path2.join;
77519
+ var join18 = path2.join;
77484
77520
  var resolve5 = path2.resolve;
77485
77521
  module2.exports = View;
77486
77522
  function View(name, options) {
@@ -77542,12 +77578,12 @@ var require_view = __commonJS({
77542
77578
  };
77543
77579
  View.prototype.resolve = function resolve6(dir, file2) {
77544
77580
  var ext = this.ext;
77545
- var path3 = join17(dir, file2);
77581
+ var path3 = join18(dir, file2);
77546
77582
  var stat = tryStat(path3);
77547
77583
  if (stat && stat.isFile()) {
77548
77584
  return path3;
77549
77585
  }
77550
- path3 = join17(dir, basename4(file2, ext), "index" + ext);
77586
+ path3 = join18(dir, basename4(file2, ext), "index" + ext);
77551
77587
  stat = tryStat(path3);
77552
77588
  if (stat && stat.isFile()) {
77553
77589
  return path3;
@@ -81192,7 +81228,7 @@ var require_send = __commonJS({
81192
81228
  var Stream = require("stream");
81193
81229
  var util2 = require("util");
81194
81230
  var extname = path2.extname;
81195
- var join17 = path2.join;
81231
+ var join18 = path2.join;
81196
81232
  var normalize = path2.normalize;
81197
81233
  var resolve5 = path2.resolve;
81198
81234
  var sep2 = path2.sep;
@@ -81364,7 +81400,7 @@ var require_send = __commonJS({
81364
81400
  return res;
81365
81401
  }
81366
81402
  parts = path3.split(sep2);
81367
- path3 = normalize(join17(root, path3));
81403
+ path3 = normalize(join18(root, path3));
81368
81404
  } else {
81369
81405
  if (UP_PATH_REGEXP.test(path3)) {
81370
81406
  debug('malicious path "%s"', path3);
@@ -81497,7 +81533,7 @@ var require_send = __commonJS({
81497
81533
  if (err2) return self.onStatError(err2);
81498
81534
  return self.error(404);
81499
81535
  }
81500
- var p = join17(path3, self._index[i]);
81536
+ var p = join18(path3, self._index[i]);
81501
81537
  debug('stat "%s"', p);
81502
81538
  fs.stat(p, function(err3, stat) {
81503
81539
  if (err3) return next(err3);
@@ -83604,7 +83640,7 @@ function resolveToken3() {
83604
83640
  return null;
83605
83641
  }
83606
83642
  function triggerCollectInBackground() {
83607
- if (isCollecting) return;
83643
+ if (isCollecting || REPLAY_SESSION) return;
83608
83644
  isCollecting = true;
83609
83645
  collect({}).then(() => {
83610
83646
  lastCollectTime = Date.now();
@@ -83621,7 +83657,8 @@ function maybeCollect() {
83621
83657
  async function apiGet2(path2, options) {
83622
83658
  const headers = {
83623
83659
  Authorization: `Bearer ${TOKEN}`,
83624
- ...options?.extraHeaders
83660
+ ...options?.extraHeaders,
83661
+ ...REPLAY_HEADERS
83625
83662
  };
83626
83663
  const response = await fetchWithTimeout(`${API_URL}/api/v1${path2}`, {
83627
83664
  headers
@@ -83636,7 +83673,8 @@ async function apiPost(path2, body, options) {
83636
83673
  const headers = {
83637
83674
  Authorization: `Bearer ${TOKEN}`,
83638
83675
  "Content-Type": "application/json",
83639
- ...options?.extraHeaders
83676
+ ...options?.extraHeaders,
83677
+ ...REPLAY_HEADERS
83640
83678
  };
83641
83679
  const response = await fetchWithTimeout(`${API_URL}/api/v1${path2}`, {
83642
83680
  method: "POST",
@@ -83681,7 +83719,7 @@ async function main3() {
83681
83719
  }
83682
83720
  warmupInBackground();
83683
83721
  }
83684
- var import_dotenv3, import_path5, import_fs6, import_os5, DEFAULT_API_URL3, CREDENTIALS_FILE5, API_URL, AGENT_SOURCE, DEFAULT_SOURCE_TOOL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
83722
+ var import_dotenv3, import_path5, import_fs6, import_os5, DEFAULT_API_URL3, CREDENTIALS_FILE5, API_URL, REPLAY_SESSION, REPLAY_HEADERS, AGENT_SOURCE, DEFAULT_SOURCE_TOOL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
83685
83723
  var init_mcp_server2 = __esm({
83686
83724
  "../service-capture/agent-sessions/mcp-server.ts"() {
83687
83725
  "use strict";
@@ -83700,6 +83738,10 @@ var init_mcp_server2 = __esm({
83700
83738
  DEFAULT_API_URL3 = SERVICE_URL_DEFAULTS.haansiApiBaseUrl;
83701
83739
  CREDENTIALS_FILE5 = (0, import_path5.join)((0, import_os5.homedir)(), ".haansi", "credentials.json");
83702
83740
  API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
83741
+ REPLAY_SESSION = /^[0-9a-f-]{36}$/i.test(
83742
+ process.env.HAANSI_REPLAY_SESSION?.trim() ?? ""
83743
+ ) ? process.env.HAANSI_REPLAY_SESSION.trim() : null;
83744
+ REPLAY_HEADERS = REPLAY_SESSION ? { "X-Haansi-Replay-Session": REPLAY_SESSION } : {};
83703
83745
  AGENT_SOURCE = process.env.HAANSI_AGENT_SOURCE?.toLowerCase();
83704
83746
  DEFAULT_SOURCE_TOOL = AGENT_SOURCE === "codex" ? "codex-mcp" : "claude-code-mcp";
83705
83747
  TOKEN = resolveToken3();
@@ -83920,6 +83962,13 @@ var init_mcp_server2 = __esm({
83920
83962
  };
83921
83963
  }
83922
83964
  case "save_solution": {
83965
+ if (REPLAY_SESSION) {
83966
+ return {
83967
+ content: [
83968
+ { type: "text", text: "Not saved: nothing is recorded here." }
83969
+ ]
83970
+ };
83971
+ }
83923
83972
  const {
83924
83973
  problem_description,
83925
83974
  solution_summary,
@@ -85562,16 +85611,125 @@ them all.`;
85562
85611
  }
85563
85612
  });
85564
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
+
85565
85719
  // src/commands/replay-tasks.ts
85566
85720
  var replay_tasks_exports = {};
85567
85721
  __export(replay_tasks_exports, {
85722
+ HAANSI_LINE: () => HAANSI_LINE,
85568
85723
  areaOf: () => areaOf,
85569
85724
  cardPrompt: () => cardPrompt,
85570
85725
  changedFiles: () => changedFiles,
85726
+ haansiText: () => haansiText,
85727
+ haansiUseOf: () => haansiUseOf,
85728
+ mcpConfig: () => mcpConfig,
85571
85729
  overlapOf: () => overlapOf,
85572
85730
  runStoredReplays: () => runStoredReplays
85573
85731
  });
85574
- function cardPrompt(task, feedback) {
85732
+ function cardPrompt(task, feedback, arm = "baseline") {
85575
85733
  return [
85576
85734
  "# Task",
85577
85735
  "",
@@ -85582,10 +85740,40 @@ function cardPrompt(task, feedback) {
85582
85740
  task.card.acceptance,
85583
85741
  ...task.card.area ? ["", "## Part of the code", "", task.card.area] : [],
85584
85742
  "",
85743
+ ...arm === "haansi" ? [HAANSI_LINE, ""] : [],
85585
85744
  ...feedback ? [FEEDBACK_HEADER, "", "```", feedback, "```", ""] : [],
85586
85745
  "Work in this repository as it is now. Do not look at later commits or other branches."
85587
85746
  ].join("\n");
85588
85747
  }
85748
+ function mcpConfig(arm, session) {
85749
+ if (arm !== "haansi" || !session) return JSON.stringify({ mcpServers: {} });
85750
+ const base = session.apiUrl.replace(/\/+$/, "");
85751
+ return JSON.stringify(
85752
+ {
85753
+ mcpServers: {
85754
+ "haansi-solutions": {
85755
+ command: "haansi",
85756
+ args: ["mcp-server"],
85757
+ env: {
85758
+ HAANSI_API_URL: base,
85759
+ HAANSI_TOKEN: session.token,
85760
+ HAANSI_REPLAY_SESSION: session.sessionId
85761
+ }
85762
+ },
85763
+ "haansi-skills": {
85764
+ type: "http",
85765
+ url: `${base}/api/v1/memory/skills/mcp`,
85766
+ headers: {
85767
+ Authorization: `Bearer ${session.token}`,
85768
+ "X-Haansi-Replay-Session": session.sessionId
85769
+ }
85770
+ }
85771
+ }
85772
+ },
85773
+ null,
85774
+ 2
85775
+ );
85776
+ }
85589
85777
  function areaOf(path2) {
85590
85778
  const dirs = path2.split("/").slice(0, -1);
85591
85779
  return dirs.length ? dirs.slice(0, 2).join("/") : ".";
@@ -85644,14 +85832,14 @@ async function runStoredReplays(options, deps) {
85644
85832
  (t) => states.get(t.taskId) === "valid" && t.testFiles.length > 0 && t.testFiles.every(safeTestFile) && SHA.test(t.baseSha) && SHA.test(t.targetSha)
85645
85833
  );
85646
85834
  const notValid = set2.tasks.length - valid.length;
85647
- let arms = options.arms;
85648
- if (arms.includes("haansi")) {
85649
- deps.log(
85650
- "The with-Haansi arm on stored tasks needs Haansi answering as of each task's start; running the baseline arm only."
85651
- );
85652
- arms = arms.filter((a) => a !== "haansi");
85653
- }
85835
+ const arms = options.arms;
85654
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
+ };
85655
85843
  const results = [];
85656
85844
  for (const task of chosen) {
85657
85845
  const label = task.name ?? task.taskKey;
@@ -85669,18 +85857,47 @@ async function runStoredReplays(options, deps) {
85669
85857
  };
85670
85858
  for (const arm of arms) {
85671
85859
  let changed = [];
85860
+ let change = "";
85672
85861
  try {
85862
+ const session = arm === "haansi" ? {
85863
+ ...await deps.openSession(task.taskId),
85864
+ apiUrl: deps.apiUrl,
85865
+ token: deps.token
85866
+ } : null;
85867
+ const configPath = `${deps.workDir()}/${task.taskId}-${arm}.mcp.json`;
85868
+ deps.writeFile(configPath, mcpConfig(arm, session));
85673
85869
  const r = await replayArm(
85674
85870
  deps,
85675
85871
  { ...options, setupCmd, testCmd },
85676
85872
  asTask,
85677
85873
  arm,
85678
85874
  null,
85679
- (feedback) => cardPrompt(task, feedback),
85875
+ (feedback) => cardPrompt(task, feedback, arm),
85680
85876
  async (dir) => {
85681
85877
  changed = await changedFiles(deps, dir, task.baseSha);
85682
- }
85878
+ if (arm === "haansi")
85879
+ change = await changeOf(deps, dir, task.baseSha);
85880
+ },
85881
+ { HAANSI_REPLAY_MCP_CONFIG: configPath }
85683
85882
  );
85883
+ deps.removeFile(configPath);
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
+ }
85684
85901
  const overlap = overlapOf(changed, task);
85685
85902
  results.push({
85686
85903
  taskId: task.taskId,
@@ -85691,10 +85908,11 @@ async function runStoredReplays(options, deps) {
85691
85908
  attempts: r.attempts,
85692
85909
  maxAttempts: r.maxAttempts,
85693
85910
  durationMs: r.durationMs,
85694
- overlap
85911
+ overlap,
85912
+ haansi
85695
85913
  });
85696
85914
  deps.log(
85697
- `${r.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${label} (${r.attempts}/${r.maxAttempts} attempts; files ${pct(overlap.fileRecall)} of the target's, ${pct(overlap.filePrecision)} of its own on target)`
85915
+ `${r.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${label} (${r.attempts}/${r.maxAttempts} attempts; files ${pct(overlap.fileRecall)} of the target's, ${pct(overlap.filePrecision)} of its own on target${haansi ? `; ${haansiText(haansi)}` : ""})`
85698
85916
  );
85699
85917
  } catch (err2) {
85700
85918
  deps.log(
@@ -85721,14 +85939,28 @@ async function runStoredReplays(options, deps) {
85721
85939
  }
85722
85940
  return { results, notValid, recorded };
85723
85941
  }
85724
- var FEEDBACK_HEADER, REPORT_CHUNK2, share, pct;
85942
+ function haansiUseOf(calls) {
85943
+ return {
85944
+ calls: calls.length,
85945
+ returned: calls.reduce((n, c) => n + c.returnedIds.length, 0)
85946
+ };
85947
+ }
85948
+ function haansiText(use) {
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})` : ""}`;
85953
+ }
85954
+ var FEEDBACK_HEADER, REPORT_CHUNK2, HAANSI_LINE, share, pct;
85725
85955
  var init_replay_tasks = __esm({
85726
85956
  "src/commands/replay-tasks.ts"() {
85727
85957
  "use strict";
85728
85958
  init_replay_calibrate();
85959
+ init_replay_judge();
85729
85960
  init_replay();
85730
85961
  FEEDBACK_HEADER = "Your previous attempt did not pass the tests. Their output ends with:";
85731
85962
  REPORT_CHUNK2 = 200;
85963
+ HAANSI_LINE = "Haansi's MCP servers are available to you: they give access to your organization's knowledge (solutions to past problems, team skills, digests, guides and Exchange). Use them if they help.";
85732
85964
  share = (part, whole) => whole === 0 ? null : Math.round(part / whole * 1e3) / 1e3;
85733
85965
  pct = (v) => v === null ? "n/a" : `${Math.round(v * 100)}%`;
85734
85966
  }
@@ -85902,9 +86134,9 @@ async function cleanClone(deps, dir, sha, unreachable) {
85902
86134
  function cleanCheckout(deps, dir, task) {
85903
86135
  return cleanClone(deps, dir, task.baseSha, task.targetSha);
85904
86136
  }
85905
- async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect) {
86137
+ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect, agentEnv = {}) {
85906
86138
  const started = deps.now();
85907
- const dir = (0, import_node_path10.join)(
86139
+ const dir = (0, import_node_path11.join)(
85908
86140
  deps.workDir(),
85909
86141
  `${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
85910
86142
  );
@@ -85930,10 +86162,15 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
85930
86162
  }
85931
86163
  deps.log(`\u2026 ${label}: set up in ${since(at)}`);
85932
86164
  }
85933
- 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");
86166
+ let mcpConfigFile = agentEnv.HAANSI_REPLAY_MCP_CONFIG;
86167
+ if (!mcpConfigFile) {
86168
+ mcpConfigFile = `${dir}.mcp.json`;
86169
+ deps.writeFile(mcpConfigFile, JSON.stringify({ mcpServers: {} }));
86170
+ }
85934
86171
  if (arm === "haansi" && playbook)
85935
86172
  deps.writeFile(
85936
- (0, import_node_path10.join)(dir, ".haansi-replay", "playbook.md"),
86173
+ (0, import_node_path11.join)(dir, ".haansi-replay", "playbook.md"),
85937
86174
  playbook.skillMd
85938
86175
  );
85939
86176
  let feedback = null;
@@ -85947,14 +86184,19 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
85947
86184
  await deps.shell(
85948
86185
  options.agentCmd,
85949
86186
  dir,
85950
- { HAANSI_REPLAY_PROMPT: prompt2, HAANSI_REPLAY_PROMPT_FILE: promptFile },
86187
+ {
86188
+ ...agentEnv,
86189
+ HAANSI_REPLAY_MCP_CONFIG: mcpConfigFile,
86190
+ HAANSI_REPLAY_PROMPT: prompt2,
86191
+ HAANSI_REPLAY_PROMPT_FILE: promptFile
86192
+ },
85951
86193
  options.timeoutMs
85952
86194
  );
85953
- 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))]));
85954
86196
  for (const f of files) {
85955
86197
  const content = await deps.showFile(task.targetSha, f);
85956
- if (content === null) deps.removeFile((0, import_node_path10.join)(dir, f));
85957
- 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);
85958
86200
  }
85959
86201
  const testAt = deps.now();
85960
86202
  deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
@@ -85984,8 +86226,8 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
85984
86226
  };
85985
86227
  }
85986
86228
  for (const [file2, content] of own) {
85987
- if (content === null) deps.removeFile((0, import_node_path10.join)(dir, file2));
85988
- 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);
85989
86231
  }
85990
86232
  feedback = test.output.slice(-FEEDBACK_CHARS);
85991
86233
  }
@@ -86001,6 +86243,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86001
86243
  };
86002
86244
  } finally {
86003
86245
  deps.removeDir(dir);
86246
+ if (!agentEnv.HAANSI_REPLAY_MCP_CONFIG) deps.removeFile(`${dir}.mcp.json`);
86004
86247
  }
86005
86248
  }
86006
86249
  async function runReplays(options, deps) {
@@ -86134,7 +86377,7 @@ function localDeps(work) {
86134
86377
  repoDir: process.cwd(),
86135
86378
  workDir: () => work,
86136
86379
  writeFile: (path2, content) => {
86137
- (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 });
86138
86381
  (0, import_node_fs11.writeFileSync)(path2, content);
86139
86382
  },
86140
86383
  readFile: (path2) => (0, import_node_fs11.existsSync)(path2) ? (0, import_node_fs11.readFileSync)(path2, "utf-8") : null,
@@ -86163,7 +86406,7 @@ async function replay(argv) {
86163
86406
  await replayStored(options, token, apiUrl2);
86164
86407
  return;
86165
86408
  }
86166
- 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-"));
86167
86410
  try {
86168
86411
  const { results, skipped, recorded } = await runReplays(options, {
86169
86412
  getTasks: async (familyId, cutoff) => (await apiGet3(
@@ -86200,7 +86443,7 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
86200
86443
  async function replayStored(options, token, apiUrl2) {
86201
86444
  const { runStoredReplays: runStoredReplays2 } = await Promise.resolve().then(() => (init_replay_tasks(), replay_tasks_exports));
86202
86445
  const { cliVersion: cliVersion2 } = await Promise.resolve().then(() => (init_replay_calibrate(), replay_calibrate_exports));
86203
- 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-"));
86204
86447
  try {
86205
86448
  const { results, notValid, recorded } = await runStoredReplays2(options, {
86206
86449
  ...localDeps(work),
@@ -86222,12 +86465,29 @@ async function replayStored(options, token, apiUrl2) {
86222
86465
  apiUrl2
86223
86466
  ),
86224
86467
  env: process.env,
86225
- cliVersion: cliVersion2()
86468
+ cliVersion: cliVersion2(),
86469
+ openSession: async (taskId) => await apiPost2(
86470
+ `/memory/replays/tasks/${encodeURIComponent(taskId)}/sessions`,
86471
+ {},
86472
+ token,
86473
+ apiUrl2
86474
+ ),
86475
+ getCalls: (sessionId) => apiGet3(
86476
+ `/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls?include=text`,
86477
+ token,
86478
+ apiUrl2
86479
+ ),
86480
+ getJudgePrompt: () => apiGet3("/memory/replays/prompts/use-judge", token, apiUrl2),
86481
+ apiUrl: apiUrl2,
86482
+ token
86226
86483
  });
86227
- const passed = results.filter((r) => r.passed).length;
86484
+ const arm = (a) => {
86485
+ const mine = results.filter((r) => r.arm === a);
86486
+ return `${mine.filter((r) => r.passed).length}/${mine.length}`;
86487
+ };
86228
86488
  console.log(
86229
86489
  `
86230
- Baseline: ${passed}/${results.length} passed \xB7 ${notValid} task(s) not valid to replay`
86490
+ Baseline: ${arm("baseline")} passed \xB7 With Haansi: ${arm("haansi")} passed \xB7 ${notValid} task(s) not valid to replay`
86231
86491
  );
86232
86492
  console.log(
86233
86493
  recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} result(s) to Haansi.`
@@ -86239,14 +86499,14 @@ Baseline: ${passed}/${results.length} passed \xB7 ${notValid} task(s) not valid
86239
86499
  (0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
86240
86500
  }
86241
86501
  }
86242
- 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;
86243
86503
  var init_replay = __esm({
86244
86504
  "src/commands/replay.ts"() {
86245
86505
  "use strict";
86246
86506
  import_node_child_process4 = require("node:child_process");
86247
86507
  import_node_fs11 = require("node:fs");
86248
86508
  import_node_os10 = require("node:os");
86249
- import_node_path10 = require("node:path");
86509
+ import_node_path11 = require("node:path");
86250
86510
  init_paths();
86251
86511
  init_api2();
86252
86512
  SHA = /^[0-9a-f]{7,40}$/i;
@@ -86272,7 +86532,7 @@ repository in Haansi (Settings \u2192 Data sources) for any not given here.
86272
86532
 
86273
86533
  Run from a clone of the repository. Example (GitHub Actions):
86274
86534
  haansi replay run --family "$FAMILY_ID" --setup-cmd 'npm ci' \\
86275
- --agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits' \\
86535
+ --agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits --mcp-config "$HAANSI_REPLAY_MCP_CONFIG"' \\
86276
86536
  --test-cmd 'npx vitest run $HAANSI_REPLAY_TEST_FILES' \\
86277
86537
  --agent-name claude-code --model claude-sonnet-5-5`;
86278
86538
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haansi",
3
- "version": "0.1.56",
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"