haansi 0.1.56 → 0.1.57

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 +142 -25
  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.57",
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",
@@ -19315,7 +19333,11 @@ var init_exclusions = __esm({
19315
19333
  "The CI's setup and test commands an org admin or CI owner typed, runtime versions, the runner image and the CLI version."
19316
19334
  ],
19317
19335
  calibration_runs: ["operational", "Exit codes of the test runs."],
19318
- calibration_run_ref: ["not-personal", "A link to the customer's CI run."]
19336
+ calibration_run_ref: ["not-personal", "A link to the customer's CI run."],
19337
+ review_state: [
19338
+ "operational",
19339
+ "Closed CHECK vocabulary (approved, excluded)."
19340
+ ]
19319
19341
  }),
19320
19342
  ...exchangeExclusions(INTEL, "problem_family_members", {
19321
19343
  process_id: [
@@ -26583,6 +26605,12 @@ var init_table_registry = __esm({
26583
26605
  entry("intelligence", INTELLIGENCE_DB, "replay_task_results", "direct", {
26584
26606
  scopingColumn: "organization_id"
26585
26607
  }),
26608
+ entry("intelligence", INTELLIGENCE_DB, "replay_sessions", "direct", {
26609
+ scopingColumn: "organization_id"
26610
+ }),
26611
+ entry("intelligence", INTELLIGENCE_DB, "replay_session_calls", "direct", {
26612
+ scopingColumn: "organization_id"
26613
+ }),
26586
26614
  // A person's revert verdicts, the checks that validate a change and the
26587
26615
  // replay commands, per repository (outcome evidence v2 plan).
26588
26616
  entry("intelligence", INTELLIGENCE_DB, "revert_labels", "direct", {
@@ -83604,7 +83632,7 @@ function resolveToken3() {
83604
83632
  return null;
83605
83633
  }
83606
83634
  function triggerCollectInBackground() {
83607
- if (isCollecting) return;
83635
+ if (isCollecting || REPLAY_SESSION) return;
83608
83636
  isCollecting = true;
83609
83637
  collect({}).then(() => {
83610
83638
  lastCollectTime = Date.now();
@@ -83621,7 +83649,8 @@ function maybeCollect() {
83621
83649
  async function apiGet2(path2, options) {
83622
83650
  const headers = {
83623
83651
  Authorization: `Bearer ${TOKEN}`,
83624
- ...options?.extraHeaders
83652
+ ...options?.extraHeaders,
83653
+ ...REPLAY_HEADERS
83625
83654
  };
83626
83655
  const response = await fetchWithTimeout(`${API_URL}/api/v1${path2}`, {
83627
83656
  headers
@@ -83636,7 +83665,8 @@ async function apiPost(path2, body, options) {
83636
83665
  const headers = {
83637
83666
  Authorization: `Bearer ${TOKEN}`,
83638
83667
  "Content-Type": "application/json",
83639
- ...options?.extraHeaders
83668
+ ...options?.extraHeaders,
83669
+ ...REPLAY_HEADERS
83640
83670
  };
83641
83671
  const response = await fetchWithTimeout(`${API_URL}/api/v1${path2}`, {
83642
83672
  method: "POST",
@@ -83681,7 +83711,7 @@ async function main3() {
83681
83711
  }
83682
83712
  warmupInBackground();
83683
83713
  }
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;
83714
+ 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
83715
  var init_mcp_server2 = __esm({
83686
83716
  "../service-capture/agent-sessions/mcp-server.ts"() {
83687
83717
  "use strict";
@@ -83700,6 +83730,10 @@ var init_mcp_server2 = __esm({
83700
83730
  DEFAULT_API_URL3 = SERVICE_URL_DEFAULTS.haansiApiBaseUrl;
83701
83731
  CREDENTIALS_FILE5 = (0, import_path5.join)((0, import_os5.homedir)(), ".haansi", "credentials.json");
83702
83732
  API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
83733
+ REPLAY_SESSION = /^[0-9a-f-]{36}$/i.test(
83734
+ process.env.HAANSI_REPLAY_SESSION?.trim() ?? ""
83735
+ ) ? process.env.HAANSI_REPLAY_SESSION.trim() : null;
83736
+ REPLAY_HEADERS = REPLAY_SESSION ? { "X-Haansi-Replay-Session": REPLAY_SESSION } : {};
83703
83737
  AGENT_SOURCE = process.env.HAANSI_AGENT_SOURCE?.toLowerCase();
83704
83738
  DEFAULT_SOURCE_TOOL = AGENT_SOURCE === "codex" ? "codex-mcp" : "claude-code-mcp";
83705
83739
  TOKEN = resolveToken3();
@@ -83920,6 +83954,13 @@ var init_mcp_server2 = __esm({
83920
83954
  };
83921
83955
  }
83922
83956
  case "save_solution": {
83957
+ if (REPLAY_SESSION) {
83958
+ return {
83959
+ content: [
83960
+ { type: "text", text: "Not saved: nothing is recorded here." }
83961
+ ]
83962
+ };
83963
+ }
83923
83964
  const {
83924
83965
  problem_description,
83925
83966
  solution_summary,
@@ -85565,13 +85606,17 @@ them all.`;
85565
85606
  // src/commands/replay-tasks.ts
85566
85607
  var replay_tasks_exports = {};
85567
85608
  __export(replay_tasks_exports, {
85609
+ HAANSI_LINE: () => HAANSI_LINE,
85568
85610
  areaOf: () => areaOf,
85569
85611
  cardPrompt: () => cardPrompt,
85570
85612
  changedFiles: () => changedFiles,
85613
+ haansiText: () => haansiText,
85614
+ haansiUseOf: () => haansiUseOf,
85615
+ mcpConfig: () => mcpConfig,
85571
85616
  overlapOf: () => overlapOf,
85572
85617
  runStoredReplays: () => runStoredReplays
85573
85618
  });
85574
- function cardPrompt(task, feedback) {
85619
+ function cardPrompt(task, feedback, arm = "baseline") {
85575
85620
  return [
85576
85621
  "# Task",
85577
85622
  "",
@@ -85582,10 +85627,40 @@ function cardPrompt(task, feedback) {
85582
85627
  task.card.acceptance,
85583
85628
  ...task.card.area ? ["", "## Part of the code", "", task.card.area] : [],
85584
85629
  "",
85630
+ ...arm === "haansi" ? [HAANSI_LINE, ""] : [],
85585
85631
  ...feedback ? [FEEDBACK_HEADER, "", "```", feedback, "```", ""] : [],
85586
85632
  "Work in this repository as it is now. Do not look at later commits or other branches."
85587
85633
  ].join("\n");
85588
85634
  }
85635
+ function mcpConfig(arm, session) {
85636
+ if (arm !== "haansi" || !session) return JSON.stringify({ mcpServers: {} });
85637
+ const base = session.apiUrl.replace(/\/+$/, "");
85638
+ return JSON.stringify(
85639
+ {
85640
+ mcpServers: {
85641
+ "haansi-solutions": {
85642
+ command: "haansi",
85643
+ args: ["mcp-server"],
85644
+ env: {
85645
+ HAANSI_API_URL: base,
85646
+ HAANSI_TOKEN: session.token,
85647
+ HAANSI_REPLAY_SESSION: session.sessionId
85648
+ }
85649
+ },
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
+ }
85658
+ }
85659
+ },
85660
+ null,
85661
+ 2
85662
+ );
85663
+ }
85589
85664
  function areaOf(path2) {
85590
85665
  const dirs = path2.split("/").slice(0, -1);
85591
85666
  return dirs.length ? dirs.slice(0, 2).join("/") : ".";
@@ -85644,13 +85719,7 @@ async function runStoredReplays(options, deps) {
85644
85719
  (t) => states.get(t.taskId) === "valid" && t.testFiles.length > 0 && t.testFiles.every(safeTestFile) && SHA.test(t.baseSha) && SHA.test(t.targetSha)
85645
85720
  );
85646
85721
  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
- }
85722
+ const arms = options.arms;
85654
85723
  const chosen = options.limit ? valid.slice(0, options.limit) : valid;
85655
85724
  const results = [];
85656
85725
  for (const task of chosen) {
@@ -85670,17 +85739,27 @@ async function runStoredReplays(options, deps) {
85670
85739
  for (const arm of arms) {
85671
85740
  let changed = [];
85672
85741
  try {
85742
+ const session = arm === "haansi" ? {
85743
+ ...await deps.openSession(task.taskId),
85744
+ apiUrl: deps.apiUrl,
85745
+ token: deps.token
85746
+ } : null;
85747
+ const configPath = `${deps.workDir()}/${task.taskId}-${arm}.mcp.json`;
85748
+ deps.writeFile(configPath, mcpConfig(arm, session));
85673
85749
  const r = await replayArm(
85674
85750
  deps,
85675
85751
  { ...options, setupCmd, testCmd },
85676
85752
  asTask,
85677
85753
  arm,
85678
85754
  null,
85679
- (feedback) => cardPrompt(task, feedback),
85755
+ (feedback) => cardPrompt(task, feedback, arm),
85680
85756
  async (dir) => {
85681
85757
  changed = await changedFiles(deps, dir, task.baseSha);
85682
- }
85758
+ },
85759
+ { HAANSI_REPLAY_MCP_CONFIG: configPath }
85683
85760
  );
85761
+ deps.removeFile(configPath);
85762
+ const haansi = session ? haansiUseOf((await deps.getCalls(session.sessionId)).calls) : null;
85684
85763
  const overlap = overlapOf(changed, task);
85685
85764
  results.push({
85686
85765
  taskId: task.taskId,
@@ -85691,10 +85770,11 @@ async function runStoredReplays(options, deps) {
85691
85770
  attempts: r.attempts,
85692
85771
  maxAttempts: r.maxAttempts,
85693
85772
  durationMs: r.durationMs,
85694
- overlap
85773
+ overlap,
85774
+ haansi
85695
85775
  });
85696
85776
  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)`
85777
+ `${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
85778
  );
85699
85779
  } catch (err2) {
85700
85780
  deps.log(
@@ -85721,7 +85801,16 @@ async function runStoredReplays(options, deps) {
85721
85801
  }
85722
85802
  return { results, notValid, recorded };
85723
85803
  }
85724
- var FEEDBACK_HEADER, REPORT_CHUNK2, share, pct;
85804
+ function haansiUseOf(calls) {
85805
+ return {
85806
+ calls: calls.length,
85807
+ returned: calls.reduce((n, c) => n + c.returnedIds.length, 0)
85808
+ };
85809
+ }
85810
+ 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`;
85812
+ }
85813
+ var FEEDBACK_HEADER, REPORT_CHUNK2, HAANSI_LINE, share, pct;
85725
85814
  var init_replay_tasks = __esm({
85726
85815
  "src/commands/replay-tasks.ts"() {
85727
85816
  "use strict";
@@ -85729,6 +85818,7 @@ var init_replay_tasks = __esm({
85729
85818
  init_replay();
85730
85819
  FEEDBACK_HEADER = "Your previous attempt did not pass the tests. Their output ends with:";
85731
85820
  REPORT_CHUNK2 = 200;
85821
+ 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
85822
  share = (part, whole) => whole === 0 ? null : Math.round(part / whole * 1e3) / 1e3;
85733
85823
  pct = (v) => v === null ? "n/a" : `${Math.round(v * 100)}%`;
85734
85824
  }
@@ -85902,7 +85992,7 @@ async function cleanClone(deps, dir, sha, unreachable) {
85902
85992
  function cleanCheckout(deps, dir, task) {
85903
85993
  return cleanClone(deps, dir, task.baseSha, task.targetSha);
85904
85994
  }
85905
- async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect) {
85995
+ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect, agentEnv = {}) {
85906
85996
  const started = deps.now();
85907
85997
  const dir = (0, import_node_path10.join)(
85908
85998
  deps.workDir(),
@@ -85931,6 +86021,11 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
85931
86021
  deps.log(`\u2026 ${label}: set up in ${since(at)}`);
85932
86022
  }
85933
86023
  const promptFile = (0, import_node_path10.join)(dir, ".haansi-replay", "prompt.md");
86024
+ let mcpConfigFile = agentEnv.HAANSI_REPLAY_MCP_CONFIG;
86025
+ if (!mcpConfigFile) {
86026
+ mcpConfigFile = `${dir}.mcp.json`;
86027
+ deps.writeFile(mcpConfigFile, JSON.stringify({ mcpServers: {} }));
86028
+ }
85934
86029
  if (arm === "haansi" && playbook)
85935
86030
  deps.writeFile(
85936
86031
  (0, import_node_path10.join)(dir, ".haansi-replay", "playbook.md"),
@@ -85947,7 +86042,12 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
85947
86042
  await deps.shell(
85948
86043
  options.agentCmd,
85949
86044
  dir,
85950
- { HAANSI_REPLAY_PROMPT: prompt2, HAANSI_REPLAY_PROMPT_FILE: promptFile },
86045
+ {
86046
+ ...agentEnv,
86047
+ HAANSI_REPLAY_MCP_CONFIG: mcpConfigFile,
86048
+ HAANSI_REPLAY_PROMPT: prompt2,
86049
+ HAANSI_REPLAY_PROMPT_FILE: promptFile
86050
+ },
85951
86051
  options.timeoutMs
85952
86052
  );
85953
86053
  const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path10.join)(dir, f))]));
@@ -86001,6 +86101,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
86001
86101
  };
86002
86102
  } finally {
86003
86103
  deps.removeDir(dir);
86104
+ if (!agentEnv.HAANSI_REPLAY_MCP_CONFIG) deps.removeFile(`${dir}.mcp.json`);
86004
86105
  }
86005
86106
  }
86006
86107
  async function runReplays(options, deps) {
@@ -86222,12 +86323,28 @@ async function replayStored(options, token, apiUrl2) {
86222
86323
  apiUrl2
86223
86324
  ),
86224
86325
  env: process.env,
86225
- cliVersion: cliVersion2()
86326
+ cliVersion: cliVersion2(),
86327
+ openSession: async (taskId) => await apiPost2(
86328
+ `/memory/replays/tasks/${encodeURIComponent(taskId)}/sessions`,
86329
+ {},
86330
+ token,
86331
+ apiUrl2
86332
+ ),
86333
+ getCalls: (sessionId) => apiGet3(
86334
+ `/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls`,
86335
+ token,
86336
+ apiUrl2
86337
+ ),
86338
+ apiUrl: apiUrl2,
86339
+ token
86226
86340
  });
86227
- const passed = results.filter((r) => r.passed).length;
86341
+ const arm = (a) => {
86342
+ const mine = results.filter((r) => r.arm === a);
86343
+ return `${mine.filter((r) => r.passed).length}/${mine.length}`;
86344
+ };
86228
86345
  console.log(
86229
86346
  `
86230
- Baseline: ${passed}/${results.length} passed \xB7 ${notValid} task(s) not valid to replay`
86347
+ Baseline: ${arm("baseline")} passed \xB7 With Haansi: ${arm("haansi")} passed \xB7 ${notValid} task(s) not valid to replay`
86231
86348
  );
86232
86349
  console.log(
86233
86350
  recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} result(s) to Haansi.`
@@ -86272,7 +86389,7 @@ repository in Haansi (Settings \u2192 Data sources) for any not given here.
86272
86389
 
86273
86390
  Run from a clone of the repository. Example (GitHub Actions):
86274
86391
  haansi replay run --family "$FAMILY_ID" --setup-cmd 'npm ci' \\
86275
- --agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits' \\
86392
+ --agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits --mcp-config "$HAANSI_REPLAY_MCP_CONFIG"' \\
86276
86393
  --test-cmd 'npx vitest run $HAANSI_REPLAY_TEST_FILES' \\
86277
86394
  --agent-name claude-code --model claude-sonnet-5-5`;
86278
86395
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haansi",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
5
5
  "bin": {
6
6
  "haansi": "./dist/haansi.js"