haansi 0.1.55 → 0.1.56

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 +281 -19
  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.55",
98
+ version: "0.1.56",
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,16 @@ 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_task_results", {
19224
+ inputs_fingerprint: [
19225
+ "derived",
19226
+ "A SHA-256 of the task's inputs the replay ran on; not reversible."
19227
+ ],
19228
+ arm: ["operational", "Closed CHECK vocabulary (baseline, haansi)."],
19229
+ agent: ["operational", "The agent CLI's name, e.g. claude-code."],
19230
+ model: ["operational", "The model name the replay ran with."],
19231
+ run_ref: ["not-personal", "A link to the customer's CI run."]
19232
+ }),
19223
19233
  ...exchangeExclusions(INTEL, "replay_tasks", {
19224
19234
  process_id: [
19225
19235
  "join-key",
@@ -26570,6 +26580,9 @@ var init_table_registry = __esm({
26570
26580
  entry("intelligence", INTELLIGENCE_DB, "stored_replay_tasks", "direct", {
26571
26581
  scopingColumn: "organization_id"
26572
26582
  }),
26583
+ entry("intelligence", INTELLIGENCE_DB, "replay_task_results", "direct", {
26584
+ scopingColumn: "organization_id"
26585
+ }),
26573
26586
  // A person's revert verdicts, the checks that validate a change and the
26574
26587
  // replay commands, per repository (outcome evidence v2 plan).
26575
26588
  entry("intelligence", INTELLIGENCE_DB, "revert_labels", "direct", {
@@ -85201,8 +85214,11 @@ __export(replay_calibrate_exports, {
85201
85214
  RUNTIMES: () => RUNTIMES,
85202
85215
  calibrate: () => calibrate,
85203
85216
  calibrateTask: () => calibrateTask,
85217
+ calibrateTaskOptions: () => calibrateTaskOptions,
85218
+ cliVersion: () => cliVersion,
85204
85219
  environmentOf: () => environmentOf,
85205
85220
  parseCalibrateArgs: () => parseCalibrateArgs,
85221
+ repositoryOf: () => repositoryOf,
85206
85222
  runCalibration: () => runCalibration,
85207
85223
  runnerOf: () => runnerOf
85208
85224
  });
@@ -85245,6 +85261,19 @@ function parseCalibrateArgs(argv) {
85245
85261
  errors.push("--timeout-min must be a positive number.");
85246
85262
  return { options, errors };
85247
85263
  }
85264
+ function calibrateTaskOptions(run3, commands) {
85265
+ return {
85266
+ setupCmd: commands.setupCmd,
85267
+ testCmd: commands.testCmd,
85268
+ repoCommands: false,
85269
+ runs: 2,
85270
+ force: false,
85271
+ limit: run3.limit,
85272
+ timeoutMs: run3.timeoutMs,
85273
+ runRef: run3.runRef,
85274
+ dryRun: run3.dryRun
85275
+ };
85276
+ }
85248
85277
  function runnerOf(env) {
85249
85278
  if (env.ImageOS && env.ImageVersion)
85250
85279
  return `${env.ImageOS}-${env.ImageVersion}`;
@@ -85357,12 +85386,16 @@ async function calibrateTask(deps, options, commands, task) {
85357
85386
  }
85358
85387
  return done("fails_at_base_passes_at_target");
85359
85388
  }
85360
- async function runCalibration(options, deps) {
85389
+ async function repositoryOf(deps) {
85361
85390
  const remote = await deps.git(["remote", "get-url", "origin"], deps.repoDir);
85362
85391
  const repository = remote.code === 0 ? repositoryFromRemote(remote.output) : null;
85363
85392
  if (!repository)
85364
85393
  throw new Error("Run from a clone of the repository (no origin remote).");
85365
- const set2 = await deps.getReplaySet(repository);
85394
+ return repository;
85395
+ }
85396
+ async function runCalibration(options, deps, preloaded) {
85397
+ const repository = await repositoryOf(deps);
85398
+ const set2 = preloaded ?? await deps.getReplaySet(repository);
85366
85399
  const repo = options.repoCommands ? set2.repoCommands : null;
85367
85400
  const setupCmd = options.setupCmd ?? repo?.setupCommand ?? null;
85368
85401
  const testCmd = options.testCmd || repo?.testCommand || "";
@@ -85372,17 +85405,12 @@ async function runCalibration(options, deps) {
85372
85405
  );
85373
85406
  const environment = await environmentOf(deps, { setupCmd, testCmd });
85374
85407
  const calibrations = [];
85408
+ const states = /* @__PURE__ */ new Map();
85375
85409
  let reused = 0;
85376
85410
  let skipped = 0;
85377
85411
  const tasks = options.limit ? set2.tasks.slice(0, options.limit) : set2.tasks;
85378
85412
  for (const task of tasks) {
85379
85413
  const label = task.name ?? task.taskKey;
85380
- const c = task.calibration;
85381
- if (!options.force && c && c.inputsFingerprint === task.inputsFingerprint && c.envFingerprint === environment.fingerprint) {
85382
- reused += 1;
85383
- deps.log(`= ${c.state.padEnd(7)} ${label} (calibrated already)`);
85384
- continue;
85385
- }
85386
85414
  if (!SHA.test(task.baseSha) || !SHA.test(task.targetSha)) {
85387
85415
  skipped += 1;
85388
85416
  deps.log(`skip ${label}: its commits are not commit ids`);
@@ -85400,6 +85428,13 @@ async function runCalibration(options, deps) {
85400
85428
  deps.log(`skip ${label}: a test path is not usable`);
85401
85429
  continue;
85402
85430
  }
85431
+ const c = task.calibration;
85432
+ if (!options.force && c && c.inputsFingerprint === task.inputsFingerprint && c.envFingerprint === environment.fingerprint) {
85433
+ reused += 1;
85434
+ states.set(task.taskId, c.state);
85435
+ deps.log(`= ${c.state.padEnd(7)} ${label} (calibrated already)`);
85436
+ continue;
85437
+ }
85403
85438
  if (!await ensureCommit(deps, task.baseSha) || !await ensureCommit(deps, task.targetSha)) {
85404
85439
  skipped += 1;
85405
85440
  deps.log(`skip ${label}: its commits are not reachable from origin`);
@@ -85412,12 +85447,13 @@ async function runCalibration(options, deps) {
85412
85447
  task
85413
85448
  );
85414
85449
  calibrations.push(result);
85450
+ states.set(task.taskId, result.state);
85415
85451
  deps.log(
85416
85452
  `${result.state.padEnd(7)} ${label}: ${result.reason.replace(/_/g, " ")} (base ${result.runs.base.join(",") || "-"}; target ${result.runs.target.join(",") || "-"})`
85417
85453
  );
85418
85454
  }
85419
85455
  if (options.dryRun || calibrations.length === 0)
85420
- return { calibrations, reused, skipped, recorded: null };
85456
+ return { calibrations, reused, skipped, recorded: null, states };
85421
85457
  let recorded = 0;
85422
85458
  for (let i = 0; i < calibrations.length; i += REPORT_CHUNK) {
85423
85459
  const out = await deps.report({
@@ -85431,7 +85467,7 @@ async function runCalibration(options, deps) {
85431
85467
  `Haansi did not record ${out.refused.length} calibration(s): their task changed since it was listed.`
85432
85468
  );
85433
85469
  }
85434
- return { calibrations, reused, skipped, recorded };
85470
+ return { calibrations, reused, skipped, recorded, states };
85435
85471
  }
85436
85472
  async function calibrate(argv) {
85437
85473
  if (argv.includes("--help")) {
@@ -85526,6 +85562,178 @@ them all.`;
85526
85562
  }
85527
85563
  });
85528
85564
 
85565
+ // src/commands/replay-tasks.ts
85566
+ var replay_tasks_exports = {};
85567
+ __export(replay_tasks_exports, {
85568
+ areaOf: () => areaOf,
85569
+ cardPrompt: () => cardPrompt,
85570
+ changedFiles: () => changedFiles,
85571
+ overlapOf: () => overlapOf,
85572
+ runStoredReplays: () => runStoredReplays
85573
+ });
85574
+ function cardPrompt(task, feedback) {
85575
+ return [
85576
+ "# Task",
85577
+ "",
85578
+ task.card.problem,
85579
+ "",
85580
+ "## Done when",
85581
+ "",
85582
+ task.card.acceptance,
85583
+ ...task.card.area ? ["", "## Part of the code", "", task.card.area] : [],
85584
+ "",
85585
+ ...feedback ? [FEEDBACK_HEADER, "", "```", feedback, "```", ""] : [],
85586
+ "Work in this repository as it is now. Do not look at later commits or other branches."
85587
+ ].join("\n");
85588
+ }
85589
+ function areaOf(path2) {
85590
+ const dirs = path2.split("/").slice(0, -1);
85591
+ return dirs.length ? dirs.slice(0, 2).join("/") : ".";
85592
+ }
85593
+ async function changedFiles(deps, dir, baseSha) {
85594
+ const lines = (out) => out.code === 0 ? out.output.split("\n").map((l) => l.trim()).filter(Boolean) : [];
85595
+ const diff = await deps.git(
85596
+ ["diff", "--name-only", "--no-renames", baseSha],
85597
+ dir
85598
+ );
85599
+ const added = await deps.git(
85600
+ ["ls-files", "--others", "--exclude-standard"],
85601
+ dir
85602
+ );
85603
+ return [.../* @__PURE__ */ new Set([...lines(diff), ...lines(added)])].filter((f) => !f.startsWith(".haansi-replay/")).sort();
85604
+ }
85605
+ function overlapOf(changed, task) {
85606
+ const tests = new Set(task.testFiles);
85607
+ const mine = new Set(changed.filter((f) => !tests.has(f)));
85608
+ const theirs = new Set(task.targetFiles.filter((f) => !tests.has(f)));
85609
+ const both = [...mine].filter((f) => theirs.has(f)).length;
85610
+ const myAreas = new Set([...mine].map(areaOf));
85611
+ const theirAreas = new Set([...theirs].map(areaOf));
85612
+ const bothAreas = [...myAreas].filter((a) => theirAreas.has(a)).length;
85613
+ const known = mine.size > 0 && theirs.size > 0;
85614
+ return {
85615
+ filePrecision: known ? share(both, mine.size) : null,
85616
+ fileRecall: known ? share(both, theirs.size) : null,
85617
+ areaPrecision: known ? share(bothAreas, myAreas.size) : null,
85618
+ areaRecall: known ? share(bothAreas, theirAreas.size) : null,
85619
+ changedFiles: mine.size
85620
+ };
85621
+ }
85622
+ async function runStoredReplays(options, deps) {
85623
+ const repository = await repositoryOf(deps);
85624
+ const set2 = await deps.getReplaySet(repository);
85625
+ const repo = options.repoCommands ? set2.repoCommands : null;
85626
+ const setupCmd = options.setupCmd ?? repo?.setupCommand ?? null;
85627
+ const testCmd = options.testCmd || repo?.testCommand || "";
85628
+ if (!testCmd)
85629
+ throw new Error(
85630
+ `No test command: pass --test-cmd, or set one for ${repository} in Haansi.`
85631
+ );
85632
+ if (set2.tasks.length === 0) {
85633
+ deps.log(
85634
+ `No replay tasks for ${repository} yet: they need a card (replay task cards on for the organization).`
85635
+ );
85636
+ return { results: [], notValid: 0, recorded: null };
85637
+ }
85638
+ const { states } = await runCalibration(
85639
+ calibrateTaskOptions(options, { setupCmd, testCmd }),
85640
+ { ...deps, report: deps.reportCalibration },
85641
+ set2
85642
+ );
85643
+ const valid = set2.tasks.filter(
85644
+ (t) => states.get(t.taskId) === "valid" && t.testFiles.length > 0 && t.testFiles.every(safeTestFile) && SHA.test(t.baseSha) && SHA.test(t.targetSha)
85645
+ );
85646
+ 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
+ }
85654
+ const chosen = options.limit ? valid.slice(0, options.limit) : valid;
85655
+ const results = [];
85656
+ for (const task of chosen) {
85657
+ const label = task.name ?? task.taskKey;
85658
+ const asTask = {
85659
+ taskId: task.taskId,
85660
+ // Names the clone's directory.
85661
+ processId: task.taskId,
85662
+ name: label,
85663
+ repository: task.repository,
85664
+ baseSha: task.baseSha,
85665
+ targetPr: task.targetPr,
85666
+ targetSha: task.targetSha,
85667
+ testFiles: task.testFiles,
85668
+ context: []
85669
+ };
85670
+ for (const arm of arms) {
85671
+ let changed = [];
85672
+ try {
85673
+ const r = await replayArm(
85674
+ deps,
85675
+ { ...options, setupCmd, testCmd },
85676
+ asTask,
85677
+ arm,
85678
+ null,
85679
+ (feedback) => cardPrompt(task, feedback),
85680
+ async (dir) => {
85681
+ changed = await changedFiles(deps, dir, task.baseSha);
85682
+ }
85683
+ );
85684
+ const overlap = overlapOf(changed, task);
85685
+ results.push({
85686
+ taskId: task.taskId,
85687
+ cardVersion: task.card.version,
85688
+ inputsFingerprint: task.inputsFingerprint,
85689
+ arm,
85690
+ passed: r.passed,
85691
+ attempts: r.attempts,
85692
+ maxAttempts: r.maxAttempts,
85693
+ durationMs: r.durationMs,
85694
+ overlap
85695
+ });
85696
+ 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)`
85698
+ );
85699
+ } catch (err2) {
85700
+ deps.log(
85701
+ `error ${arm} ${label}: ${err2 instanceof Error ? err2.message : String(err2)}`
85702
+ );
85703
+ }
85704
+ }
85705
+ }
85706
+ if (options.dryRun || results.length === 0)
85707
+ return { results, notValid, recorded: null };
85708
+ let recorded = 0;
85709
+ for (let i = 0; i < results.length; i += REPORT_CHUNK2) {
85710
+ const out = await deps.reportResults({
85711
+ agent: options.agentName,
85712
+ model: options.model,
85713
+ runRef: options.runRef,
85714
+ results: results.slice(i, i + REPORT_CHUNK2)
85715
+ });
85716
+ recorded += out.recorded;
85717
+ if (out.refused.length)
85718
+ deps.log(
85719
+ `Haansi did not record ${out.refused.length} result(s): their task's card or inputs changed during the run.`
85720
+ );
85721
+ }
85722
+ return { results, notValid, recorded };
85723
+ }
85724
+ var FEEDBACK_HEADER, REPORT_CHUNK2, share, pct;
85725
+ var init_replay_tasks = __esm({
85726
+ "src/commands/replay-tasks.ts"() {
85727
+ "use strict";
85728
+ init_replay_calibrate();
85729
+ init_replay();
85730
+ FEEDBACK_HEADER = "Your previous attempt did not pass the tests. Their output ends with:";
85731
+ REPORT_CHUNK2 = 200;
85732
+ share = (part, whole) => whole === 0 ? null : Math.round(part / whole * 1e3) / 1e3;
85733
+ pct = (v) => v === null ? "n/a" : `${Math.round(v * 100)}%`;
85734
+ }
85735
+ });
85736
+
85529
85737
  // src/commands/replay.ts
85530
85738
  var replay_exports = {};
85531
85739
  __export(replay_exports, {
@@ -85585,8 +85793,8 @@ function parseReplayArgs(argv) {
85585
85793
  else if (!sub && !a.startsWith("-")) sub = a;
85586
85794
  else errors.push(`Unknown option: ${a}`);
85587
85795
  }
85588
- if (!/^[0-9a-f-]{36}$/i.test(options.familyId))
85589
- errors.push("--family <id> is required (the family's id).");
85796
+ if (options.familyId && !/^[0-9a-f-]{36}$/i.test(options.familyId))
85797
+ errors.push("--family must be a family's id.");
85590
85798
  if (!options.agentCmd) errors.push("--agent-cmd is required.");
85591
85799
  if (!options.testCmd && !options.repoCommands)
85592
85800
  errors.push(
@@ -85694,7 +85902,7 @@ async function cleanClone(deps, dir, sha, unreachable) {
85694
85902
  function cleanCheckout(deps, dir, task) {
85695
85903
  return cleanClone(deps, dir, task.baseSha, task.targetSha);
85696
85904
  }
85697
- async function replayArm(deps, options, task, arm, playbook) {
85905
+ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect) {
85698
85906
  const started = deps.now();
85699
85907
  const dir = (0, import_node_path10.join)(
85700
85908
  deps.workDir(),
@@ -85730,7 +85938,7 @@ async function replayArm(deps, options, task, arm, playbook) {
85730
85938
  );
85731
85939
  let feedback = null;
85732
85940
  for (let attempt = 1; attempt <= options.attempts; attempt++) {
85733
- const prompt2 = replayPrompt(task, arm, feedback);
85941
+ const prompt2 = promptOf(feedback);
85734
85942
  deps.writeFile(promptFile, prompt2);
85735
85943
  const agentAt = deps.now();
85736
85944
  deps.log(
@@ -85764,6 +85972,7 @@ async function replayArm(deps, options, task, arm, playbook) {
85764
85972
  deps.log(` ${line}`);
85765
85973
  }
85766
85974
  if (test.code === 0) {
85975
+ if (inspect) await inspect(dir);
85767
85976
  return {
85768
85977
  processId: task.processId,
85769
85978
  arm,
@@ -85780,6 +85989,7 @@ async function replayArm(deps, options, task, arm, playbook) {
85780
85989
  }
85781
85990
  feedback = test.output.slice(-FEEDBACK_CHARS);
85782
85991
  }
85992
+ if (inspect) await inspect(dir);
85783
85993
  return {
85784
85994
  processId: task.processId,
85785
85995
  arm,
@@ -85949,6 +86159,10 @@ async function replay(argv) {
85949
86159
  }
85950
86160
  const token = resolveToken5();
85951
86161
  const apiUrl2 = apiUrl();
86162
+ if (!options.familyId) {
86163
+ await replayStored(options, token, apiUrl2);
86164
+ return;
86165
+ }
85952
86166
  const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
85953
86167
  try {
85954
86168
  const { results, skipped, recorded } = await runReplays(options, {
@@ -85983,6 +86197,48 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
85983
86197
  (0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
85984
86198
  }
85985
86199
  }
86200
+ async function replayStored(options, token, apiUrl2) {
86201
+ const { runStoredReplays: runStoredReplays2 } = await Promise.resolve().then(() => (init_replay_tasks(), replay_tasks_exports));
86202
+ 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-"));
86204
+ try {
86205
+ const { results, notValid, recorded } = await runStoredReplays2(options, {
86206
+ ...localDeps(work),
86207
+ getReplaySet: (repository) => apiGet3(
86208
+ `/memory/replays/tasks?repository=${encodeURIComponent(repository)}`,
86209
+ token,
86210
+ apiUrl2
86211
+ ),
86212
+ reportCalibration: async (body) => await apiPost2(
86213
+ "/memory/replays/tasks/calibration",
86214
+ body,
86215
+ token,
86216
+ apiUrl2
86217
+ ),
86218
+ reportResults: async (body) => await apiPost2(
86219
+ "/memory/replays/tasks/results",
86220
+ body,
86221
+ token,
86222
+ apiUrl2
86223
+ ),
86224
+ env: process.env,
86225
+ cliVersion: cliVersion2()
86226
+ });
86227
+ const passed = results.filter((r) => r.passed).length;
86228
+ console.log(
86229
+ `
86230
+ Baseline: ${passed}/${results.length} passed \xB7 ${notValid} task(s) not valid to replay`
86231
+ );
86232
+ console.log(
86233
+ recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} result(s) to Haansi.`
86234
+ );
86235
+ } catch (err2) {
86236
+ console.error(err2 instanceof Error ? err2.message : String(err2));
86237
+ process.exitCode = 1;
86238
+ } finally {
86239
+ (0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
86240
+ }
86241
+ }
85986
86242
  var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path10, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
85987
86243
  var init_replay = __esm({
85988
86244
  "src/commands/replay.ts"() {
@@ -85998,7 +86254,7 @@ var init_replay = __esm({
85998
86254
  FEEDBACK_CHARS = 6e3;
85999
86255
  FAILURE_LINES2 = 8;
86000
86256
  REPLAY_BRANCH = "replay";
86001
- USAGE2 = `Usage: haansi replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
86257
+ USAGE2 = `Usage: haansi replay run [--family <id>] --agent-cmd "<cmd>" --test-cmd "<cmd>"
86002
86258
  [--setup-cmd "<cmd>"] [--attempts 3] [--arms baseline,haansi] [--limit n]
86003
86259
  [--cutoff <date>] [--timeout-min 30]
86004
86260
  [--agent-name <name>] [--model <name>] [--run-ref <url>] [--dry-run]
@@ -86006,6 +86262,11 @@ var init_replay = __esm({
86006
86262
 
86007
86263
  haansi replay calibrate --test-cmd "<cmd>" [--setup-cmd "<cmd>"] (see --help)
86008
86264
 
86265
+ Without --family, runs this repository's stored replay tasks: calibrates them
86266
+ first, then replays the valid ones with their task card as the prompt, graded
86267
+ by the target's tests and where the change landed. With --family, replays that
86268
+ problem family's tasks with and without its playbook.
86269
+
86009
86270
  --repo-commands uses the setup and test commands an org admin set for the
86010
86271
  repository in Haansi (Settings \u2192 Data sources) for any not given here.
86011
86272
 
@@ -86231,9 +86492,10 @@ Commands:
86231
86492
  digest. [--date YYYY-MM-DD] [--workstream <id>]
86232
86493
  digest [--week|--month|--since YYYY-MM-DD]
86233
86494
  Your weekly digest (default: the latest week)
86234
- replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
86235
- Replay a problem family's tasks in your CI, with and
86236
- without its playbook, and report the results
86495
+ replay run --agent-cmd "<cmd>" --test-cmd "<cmd>" [--family <id>]
86496
+ Replay this repository's tasks in your CI (or a
86497
+ problem family's, with and without its playbook)
86498
+ and report the results
86237
86499
  replay calibrate --test-cmd "<cmd>"
86238
86500
  Check this repository's replay tasks are valid: their
86239
86501
  tests fail at the start and pass at the fix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haansi",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "description": "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
5
5
  "bin": {
6
6
  "haansi": "./dist/haansi.js"