haansi 0.1.51 → 0.1.53
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.
- package/dist/haansi.js +140 -7
- 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.
|
|
98
|
+
version: "0.1.53",
|
|
99
99
|
description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
|
|
100
100
|
bin: {
|
|
101
101
|
haansi: "./dist/haansi.js"
|
|
@@ -19000,6 +19000,14 @@ var init_exclusions = __esm({
|
|
|
19000
19000
|
"join-key",
|
|
19001
19001
|
"A canonical object id (github:<repo>#<n>), never text."
|
|
19002
19002
|
],
|
|
19003
|
+
revert_evidence: [
|
|
19004
|
+
"not-personal",
|
|
19005
|
+
'Revert signal kinds with ledger-written details ("Revert #12", "branch revert-12-\u2026"); no PR text.'
|
|
19006
|
+
],
|
|
19007
|
+
possible_revert: [
|
|
19008
|
+
"not-personal",
|
|
19009
|
+
"A candidate revert's canonical id, time and ledger-written signals; no PR text."
|
|
19010
|
+
],
|
|
19003
19011
|
survival_14d: [
|
|
19004
19012
|
"operational",
|
|
19005
19013
|
"Closed CHECK vocabulary (held, failed, pending)."
|
|
@@ -19022,6 +19030,10 @@ var init_exclusions = __esm({
|
|
|
19022
19030
|
]
|
|
19023
19031
|
}),
|
|
19024
19032
|
...exchangeExclusions(INTEL, "workstream_attempts", {
|
|
19033
|
+
evidence: [
|
|
19034
|
+
"not-personal",
|
|
19035
|
+
"CI check names, commit ids, PR numbers and GitHub links a verdict rests on; no PR text."
|
|
19036
|
+
],
|
|
19025
19037
|
process_id: [
|
|
19026
19038
|
"join-key",
|
|
19027
19039
|
"The workstream's process_registry.process_id, joined by equality."
|
|
@@ -19112,6 +19124,10 @@ var init_exclusions = __esm({
|
|
|
19112
19124
|
"operational",
|
|
19113
19125
|
"Closed CHECK vocabulary (held, reversed, revisited)."
|
|
19114
19126
|
],
|
|
19127
|
+
outcome_evidence: [
|
|
19128
|
+
"not-personal",
|
|
19129
|
+
"PR keys (owner/repo#n), commit ids and timestamps the outcome rests on; no text."
|
|
19130
|
+
],
|
|
19115
19131
|
change_keys: [
|
|
19116
19132
|
"join-key",
|
|
19117
19133
|
"owner/repo#n of the workstream's PRs, as in workstream_attempts."
|
|
@@ -19150,6 +19166,50 @@ var init_exclusions = __esm({
|
|
|
19150
19166
|
"Counts by Actions job and step name, from CI config."
|
|
19151
19167
|
]
|
|
19152
19168
|
}),
|
|
19169
|
+
...exchangeExclusions(INTEL, "repository_checks", {
|
|
19170
|
+
repository: [
|
|
19171
|
+
"join-key",
|
|
19172
|
+
"owner/repo of the org's CI, as in change_outcomes."
|
|
19173
|
+
],
|
|
19174
|
+
check_key: [
|
|
19175
|
+
"join-key",
|
|
19176
|
+
"Normalized CI workflow and job name, matched against captured jobs."
|
|
19177
|
+
],
|
|
19178
|
+
workflow: ["not-personal", "A CI workflow's name, e.g. CI."],
|
|
19179
|
+
job: ["not-personal", "A CI job's name, e.g. Unit tests."],
|
|
19180
|
+
kind: [
|
|
19181
|
+
"operational",
|
|
19182
|
+
"Closed CHECK vocabulary (test, typecheck, build, lint, format, deploy, other)."
|
|
19183
|
+
]
|
|
19184
|
+
}),
|
|
19185
|
+
...exchangeExclusions(INTEL, "revert_labels", {
|
|
19186
|
+
repository: [
|
|
19187
|
+
"join-key",
|
|
19188
|
+
"owner/repo of the reverted PR, as in change_outcomes."
|
|
19189
|
+
],
|
|
19190
|
+
reverted_by: [
|
|
19191
|
+
"join-key",
|
|
19192
|
+
"Canonical id of the reverting PR or commit (github:owner/repo#n, github:commit:<sha>)."
|
|
19193
|
+
],
|
|
19194
|
+
label: [
|
|
19195
|
+
"operational",
|
|
19196
|
+
"Closed CHECK vocabulary (confirmed, rejected, cleared)."
|
|
19197
|
+
]
|
|
19198
|
+
}),
|
|
19199
|
+
...exchangeExclusions(INTEL, "repository_replay_settings", {
|
|
19200
|
+
repository: [
|
|
19201
|
+
"join-key",
|
|
19202
|
+
"owner/repo, as in replay_tasks and repository_checks."
|
|
19203
|
+
],
|
|
19204
|
+
setup_command: [
|
|
19205
|
+
"not-personal",
|
|
19206
|
+
"A shell command an org admin typed for its CI (e.g. npm ci); run by the customer's own CI."
|
|
19207
|
+
],
|
|
19208
|
+
test_command: [
|
|
19209
|
+
"not-personal",
|
|
19210
|
+
"A shell command an org admin typed for its CI (e.g. npm run test); run by the customer's own CI."
|
|
19211
|
+
]
|
|
19212
|
+
}),
|
|
19153
19213
|
...exchangeExclusions(INTEL, "replay_results", {
|
|
19154
19214
|
process_id: [
|
|
19155
19215
|
"join-key",
|
|
@@ -85057,13 +85117,16 @@ __export(replay_exports, {
|
|
|
85057
85117
|
runCommand: () => runCommand,
|
|
85058
85118
|
runReplays: () => runReplays,
|
|
85059
85119
|
safeTestFile: () => safeTestFile,
|
|
85060
|
-
skipReason: () => skipReason
|
|
85120
|
+
skipReason: () => skipReason,
|
|
85121
|
+
taskCommands: () => taskCommands
|
|
85061
85122
|
});
|
|
85062
85123
|
function parseReplayArgs(argv) {
|
|
85063
85124
|
const options = {
|
|
85064
85125
|
familyId: "",
|
|
85065
85126
|
agentCmd: "",
|
|
85066
85127
|
testCmd: "",
|
|
85128
|
+
setupCmd: null,
|
|
85129
|
+
repoCommands: false,
|
|
85067
85130
|
attempts: 3,
|
|
85068
85131
|
arms: [...ARMS],
|
|
85069
85132
|
limit: null,
|
|
@@ -85082,6 +85145,7 @@ function parseReplayArgs(argv) {
|
|
|
85082
85145
|
if (a === "--family") options.familyId = next();
|
|
85083
85146
|
else if (a === "--agent-cmd") options.agentCmd = next();
|
|
85084
85147
|
else if (a === "--test-cmd") options.testCmd = next();
|
|
85148
|
+
else if (a === "--setup-cmd") options.setupCmd = next() || null;
|
|
85085
85149
|
else if (a === "--attempts") options.attempts = Number(next());
|
|
85086
85150
|
else if (a === "--limit") options.limit = Number(next());
|
|
85087
85151
|
else if (a === "--cutoff") options.cutoff = next();
|
|
@@ -85090,6 +85154,7 @@ function parseReplayArgs(argv) {
|
|
|
85090
85154
|
else if (a === "--model") options.model = next();
|
|
85091
85155
|
else if (a === "--run-ref") options.runRef = next();
|
|
85092
85156
|
else if (a === "--dry-run") options.dryRun = true;
|
|
85157
|
+
else if (a === "--repo-commands") options.repoCommands = true;
|
|
85093
85158
|
else if (a === "--arms")
|
|
85094
85159
|
options.arms = next().split(",").map((s) => s.trim()).filter(Boolean);
|
|
85095
85160
|
else if (!sub && !a.startsWith("-")) sub = a;
|
|
@@ -85098,7 +85163,10 @@ function parseReplayArgs(argv) {
|
|
|
85098
85163
|
if (!/^[0-9a-f-]{36}$/i.test(options.familyId))
|
|
85099
85164
|
errors.push("--family <id> is required (the family's id).");
|
|
85100
85165
|
if (!options.agentCmd) errors.push("--agent-cmd is required.");
|
|
85101
|
-
if (!options.testCmd
|
|
85166
|
+
if (!options.testCmd && !options.repoCommands)
|
|
85167
|
+
errors.push(
|
|
85168
|
+
"--test-cmd is required (or --repo-commands, to use the repository's from Haansi)."
|
|
85169
|
+
);
|
|
85102
85170
|
if (!Number.isInteger(options.attempts) || options.attempts < 1 || options.attempts > 20)
|
|
85103
85171
|
errors.push("--attempts must be 1 to 20.");
|
|
85104
85172
|
if (options.limit !== null && (!Number.isInteger(options.limit) || options.limit < 1))
|
|
@@ -85122,6 +85190,16 @@ function repositoryFromRemote(remote) {
|
|
|
85122
85190
|
function safeTestFile(path2) {
|
|
85123
85191
|
return path2.length > 0 && !path2.startsWith("/") && !path2.startsWith("-") && !path2.split("/").includes("..") && !/[\u0000-\u001f]/.test(path2);
|
|
85124
85192
|
}
|
|
85193
|
+
function taskCommands(options, task) {
|
|
85194
|
+
const repo = options.repoCommands ? task.repoCommands : null;
|
|
85195
|
+
const setupCmd = options.setupCmd ?? repo?.setupCommand ?? null;
|
|
85196
|
+
const testCmd = options.testCmd || repo?.testCommand || null;
|
|
85197
|
+
return {
|
|
85198
|
+
setupCmd,
|
|
85199
|
+
testCmd,
|
|
85200
|
+
fromHaansi: Boolean(repo) && (!options.setupCmd && setupCmd !== null || !options.testCmd && testCmd !== null)
|
|
85201
|
+
};
|
|
85202
|
+
}
|
|
85125
85203
|
function skipReason(task, repository) {
|
|
85126
85204
|
if (!repository || task.repository.toLowerCase() !== repository)
|
|
85127
85205
|
return `in ${task.repository}, not this repository`;
|
|
@@ -85173,6 +85251,25 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85173
85251
|
);
|
|
85174
85252
|
if (add.code !== 0) throw new Error(`could not check out ${task.baseSha}`);
|
|
85175
85253
|
try {
|
|
85254
|
+
const label = `${task.name ?? task.processId} [${arm}]`;
|
|
85255
|
+
const since = (from) => `${Math.round((deps.now() - from) / 1e3)}s`;
|
|
85256
|
+
deps.log(`\u2026 ${label}: checked out ${task.baseSha.slice(0, 9)}`);
|
|
85257
|
+
if (options.setupCmd) {
|
|
85258
|
+
const at = deps.now();
|
|
85259
|
+
deps.log(`\u2026 ${label}: setting up`);
|
|
85260
|
+
const setup = await deps.shell(
|
|
85261
|
+
options.setupCmd,
|
|
85262
|
+
dir,
|
|
85263
|
+
{},
|
|
85264
|
+
options.timeoutMs
|
|
85265
|
+
);
|
|
85266
|
+
if (setup.code !== 0) {
|
|
85267
|
+
throw new Error(
|
|
85268
|
+
`setup failed after ${since(at)}: ${setup.output.trim().split("\n").slice(-3).join(" | ")}`
|
|
85269
|
+
);
|
|
85270
|
+
}
|
|
85271
|
+
deps.log(`\u2026 ${label}: set up in ${since(at)}`);
|
|
85272
|
+
}
|
|
85176
85273
|
const promptFile = (0, import_node_path9.join)(dir, ".haansi-replay", "prompt.md");
|
|
85177
85274
|
if (arm === "haansi" && playbook)
|
|
85178
85275
|
deps.writeFile(
|
|
@@ -85183,6 +85280,10 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85183
85280
|
for (let attempt = 1; attempt <= options.attempts; attempt++) {
|
|
85184
85281
|
const prompt2 = replayPrompt(task, arm, feedback);
|
|
85185
85282
|
deps.writeFile(promptFile, prompt2);
|
|
85283
|
+
const agentAt = deps.now();
|
|
85284
|
+
deps.log(
|
|
85285
|
+
`\u2026 ${label}: attempt ${attempt}/${options.attempts}, running the agent`
|
|
85286
|
+
);
|
|
85186
85287
|
await deps.shell(
|
|
85187
85288
|
options.agentCmd,
|
|
85188
85289
|
dir,
|
|
@@ -85196,12 +85297,21 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85196
85297
|
);
|
|
85197
85298
|
if (checkout.code !== 0)
|
|
85198
85299
|
throw new Error("could not check out the fix's tests");
|
|
85300
|
+
const testAt = deps.now();
|
|
85301
|
+
deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
|
|
85199
85302
|
const test = await deps.shell(
|
|
85200
85303
|
options.testCmd,
|
|
85201
85304
|
dir,
|
|
85202
85305
|
{ HAANSI_REPLAY_TEST_FILES: files.join(" ") },
|
|
85203
85306
|
options.timeoutMs
|
|
85204
85307
|
);
|
|
85308
|
+
deps.log(
|
|
85309
|
+
`\u2026 ${label}: tests ${test.code === 0 ? "passed" : "failed"} in ${since(testAt)}`
|
|
85310
|
+
);
|
|
85311
|
+
if (test.code !== 0) {
|
|
85312
|
+
for (const line of test.output.trim().split("\n").slice(-FAILURE_LINES))
|
|
85313
|
+
deps.log(` ${line}`);
|
|
85314
|
+
}
|
|
85205
85315
|
if (test.code === 0) {
|
|
85206
85316
|
return {
|
|
85207
85317
|
processId: task.processId,
|
|
@@ -85260,6 +85370,24 @@ async function runReplays(options, deps) {
|
|
|
85260
85370
|
const chosen = options.limit ? runnable.slice(0, options.limit) : runnable;
|
|
85261
85371
|
const results = [];
|
|
85262
85372
|
for (const task of chosen) {
|
|
85373
|
+
const commands = taskCommands(options, task);
|
|
85374
|
+
if (!commands.testCmd) {
|
|
85375
|
+
skipped += 1;
|
|
85376
|
+
deps.log(
|
|
85377
|
+
`skip ${task.name ?? task.processId}: no test command (pass --test-cmd, or set one for ${task.repository} in Haansi)`
|
|
85378
|
+
);
|
|
85379
|
+
continue;
|
|
85380
|
+
}
|
|
85381
|
+
if (commands.fromHaansi) {
|
|
85382
|
+
deps.log(
|
|
85383
|
+
`\u2026 ${task.name ?? task.processId}: using ${task.repository}'s replay commands from Haansi (setup: ${commands.setupCmd ?? "none"}; test: ${commands.testCmd})`
|
|
85384
|
+
);
|
|
85385
|
+
}
|
|
85386
|
+
const taskOptions = {
|
|
85387
|
+
...options,
|
|
85388
|
+
setupCmd: commands.setupCmd,
|
|
85389
|
+
testCmd: commands.testCmd
|
|
85390
|
+
};
|
|
85263
85391
|
if (!await ensureCommit(deps, task.baseSha) || !await ensureCommit(deps, task.targetSha)) {
|
|
85264
85392
|
skipped += 1;
|
|
85265
85393
|
deps.log(
|
|
@@ -85269,7 +85397,7 @@ async function runReplays(options, deps) {
|
|
|
85269
85397
|
}
|
|
85270
85398
|
for (const arm of arms) {
|
|
85271
85399
|
try {
|
|
85272
|
-
const result = await replayArm(deps,
|
|
85400
|
+
const result = await replayArm(deps, taskOptions, task, arm, playbook);
|
|
85273
85401
|
results.push(result);
|
|
85274
85402
|
deps.log(
|
|
85275
85403
|
`${result.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${task.name ?? task.processId} (${result.attempts}/${result.maxAttempts} attempts)`
|
|
@@ -85376,7 +85504,7 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
|
|
|
85376
85504
|
(0, import_node_fs10.rmSync)(work, { recursive: true, force: true });
|
|
85377
85505
|
}
|
|
85378
85506
|
}
|
|
85379
|
-
var import_node_child_process4, import_node_fs10, import_node_os9, import_node_path9, SHA, ARMS, FEEDBACK_CHARS, USAGE;
|
|
85507
|
+
var import_node_child_process4, import_node_fs10, import_node_os9, import_node_path9, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES, USAGE;
|
|
85380
85508
|
var init_replay = __esm({
|
|
85381
85509
|
"src/commands/replay.ts"() {
|
|
85382
85510
|
"use strict";
|
|
@@ -85389,13 +85517,18 @@ var init_replay = __esm({
|
|
|
85389
85517
|
SHA = /^[0-9a-f]{7,40}$/i;
|
|
85390
85518
|
ARMS = ["baseline", "haansi"];
|
|
85391
85519
|
FEEDBACK_CHARS = 6e3;
|
|
85520
|
+
FAILURE_LINES = 8;
|
|
85392
85521
|
USAGE = `Usage: haansi replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
|
|
85393
|
-
[--attempts 3] [--arms baseline,haansi] [--limit n]
|
|
85522
|
+
[--setup-cmd "<cmd>"] [--attempts 3] [--arms baseline,haansi] [--limit n]
|
|
85394
85523
|
[--cutoff <date>] [--timeout-min 30]
|
|
85395
85524
|
[--agent-name <name>] [--model <name>] [--run-ref <url>] [--dry-run]
|
|
85525
|
+
[--repo-commands]
|
|
85526
|
+
|
|
85527
|
+
--repo-commands uses the setup and test commands an org admin set for the
|
|
85528
|
+
repository in Haansi (Settings \u2192 Data sources) for any not given here.
|
|
85396
85529
|
|
|
85397
85530
|
Run from a clone of the repository. Example (GitHub Actions):
|
|
85398
|
-
haansi replay run --family "$FAMILY_ID" \\
|
|
85531
|
+
haansi replay run --family "$FAMILY_ID" --setup-cmd 'npm ci' \\
|
|
85399
85532
|
--agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits' \\
|
|
85400
85533
|
--test-cmd 'npx vitest run $HAANSI_REPLAY_TEST_FILES' \\
|
|
85401
85534
|
--agent-name claude-code --model claude-sonnet-5-5`;
|