haansi 0.1.54 → 0.1.55
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 +497 -53
- 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.55",
|
|
99
99
|
description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
|
|
100
100
|
bin: {
|
|
101
101
|
haansi: "./dist/haansi.js"
|
|
@@ -19236,6 +19236,77 @@ var init_exclusions = __esm({
|
|
|
19236
19236
|
"Actions workflow names from CI config that passed on the fix."
|
|
19237
19237
|
]
|
|
19238
19238
|
}),
|
|
19239
|
+
...exchangeExclusions(INTEL, "stored_replay_tasks", {
|
|
19240
|
+
task_key: [
|
|
19241
|
+
"join-key",
|
|
19242
|
+
"The task's kind with the workstream id or owner/repo#PR numbers it was cut from; upserted by equality."
|
|
19243
|
+
],
|
|
19244
|
+
kind: [
|
|
19245
|
+
"operational",
|
|
19246
|
+
"Closed CHECK vocabulary (full, milestone, recovery)."
|
|
19247
|
+
],
|
|
19248
|
+
process_id: [
|
|
19249
|
+
"join-key",
|
|
19250
|
+
"The workstream's process_registry.process_id, joined by equality."
|
|
19251
|
+
],
|
|
19252
|
+
repository: [
|
|
19253
|
+
"join-key",
|
|
19254
|
+
"owner/repo of the task's PRs, as in workstream_attempts."
|
|
19255
|
+
],
|
|
19256
|
+
base_sha: ["not-personal", "A commit SHA."],
|
|
19257
|
+
target_sha: ["not-personal", "A commit SHA."],
|
|
19258
|
+
checks: [
|
|
19259
|
+
"not-personal",
|
|
19260
|
+
"Actions workflow names from CI config that passed on the target."
|
|
19261
|
+
],
|
|
19262
|
+
score_parts: [
|
|
19263
|
+
"not-personal",
|
|
19264
|
+
"Counts and flags the replayability score rests on (files, areas, context records, gradable by tests or checks); no text."
|
|
19265
|
+
],
|
|
19266
|
+
inputs_fingerprint: [
|
|
19267
|
+
"derived",
|
|
19268
|
+
"A SHA-256 of the task's inputs; not reversible."
|
|
19269
|
+
],
|
|
19270
|
+
card_state: [
|
|
19271
|
+
"operational",
|
|
19272
|
+
"Closed CHECK vocabulary (ready, dropped, invalid)."
|
|
19273
|
+
],
|
|
19274
|
+
card_dropped_reason: [
|
|
19275
|
+
"operational",
|
|
19276
|
+
"Closed CHECK vocabulary (leak, insufficient)."
|
|
19277
|
+
],
|
|
19278
|
+
card_fingerprint: [
|
|
19279
|
+
"derived",
|
|
19280
|
+
"A SHA-256 of what the card was written from; not reversible."
|
|
19281
|
+
],
|
|
19282
|
+
card_model: ["not-personal", "The model id that wrote the card."],
|
|
19283
|
+
card_prompt_tag: [
|
|
19284
|
+
"not-personal",
|
|
19285
|
+
"The prompt version that wrote the card (replay-task-card-vN)."
|
|
19286
|
+
],
|
|
19287
|
+
calibration_state: [
|
|
19288
|
+
"operational",
|
|
19289
|
+
"Closed CHECK vocabulary (valid, invalid, flaky, error)."
|
|
19290
|
+
],
|
|
19291
|
+
calibration_reason: [
|
|
19292
|
+
"operational",
|
|
19293
|
+
"Closed CHECK vocabulary (passes_at_base, fails_at_target, \u2026)."
|
|
19294
|
+
],
|
|
19295
|
+
calibration_inputs_fingerprint: [
|
|
19296
|
+
"derived",
|
|
19297
|
+
"A SHA-256 of the task's inputs; not reversible."
|
|
19298
|
+
],
|
|
19299
|
+
calibration_env_fingerprint: [
|
|
19300
|
+
"derived",
|
|
19301
|
+
"A SHA-256 of the CI environment; not reversible."
|
|
19302
|
+
],
|
|
19303
|
+
calibration_environment: [
|
|
19304
|
+
"not-personal",
|
|
19305
|
+
"The CI's setup and test commands an org admin or CI owner typed, runtime versions, the runner image and the CLI version."
|
|
19306
|
+
],
|
|
19307
|
+
calibration_runs: ["operational", "Exit codes of the test runs."],
|
|
19308
|
+
calibration_run_ref: ["not-personal", "A link to the customer's CI run."]
|
|
19309
|
+
}),
|
|
19239
19310
|
...exchangeExclusions(INTEL, "problem_family_members", {
|
|
19240
19311
|
process_id: [
|
|
19241
19312
|
"join-key",
|
|
@@ -26495,6 +26566,25 @@ var init_table_registry = __esm({
|
|
|
26495
26566
|
entry("intelligence", INTELLIGENCE_DB, "replay_tasks", "direct", {
|
|
26496
26567
|
scopingColumn: "organization_id"
|
|
26497
26568
|
}),
|
|
26569
|
+
// Stored replay tasks proposed by the ledger (replay tasks v2 plan §4).
|
|
26570
|
+
entry("intelligence", INTELLIGENCE_DB, "stored_replay_tasks", "direct", {
|
|
26571
|
+
scopingColumn: "organization_id"
|
|
26572
|
+
}),
|
|
26573
|
+
// A person's revert verdicts, the checks that validate a change and the
|
|
26574
|
+
// replay commands, per repository (outcome evidence v2 plan).
|
|
26575
|
+
entry("intelligence", INTELLIGENCE_DB, "revert_labels", "direct", {
|
|
26576
|
+
scopingColumn: "organization_id"
|
|
26577
|
+
}),
|
|
26578
|
+
entry("intelligence", INTELLIGENCE_DB, "repository_checks", "direct", {
|
|
26579
|
+
scopingColumn: "organization_id"
|
|
26580
|
+
}),
|
|
26581
|
+
entry(
|
|
26582
|
+
"intelligence",
|
|
26583
|
+
INTELLIGENCE_DB,
|
|
26584
|
+
"repository_replay_settings",
|
|
26585
|
+
"direct",
|
|
26586
|
+
{ scopingColumn: "organization_id" }
|
|
26587
|
+
),
|
|
26498
26588
|
entry("intelligence", INTELLIGENCE_DB, "problem_family_feedback", "direct", {
|
|
26499
26589
|
scopingColumn: "organization_id"
|
|
26500
26590
|
}),
|
|
@@ -77377,7 +77467,7 @@ var require_view = __commonJS({
|
|
|
77377
77467
|
var dirname5 = path2.dirname;
|
|
77378
77468
|
var basename4 = path2.basename;
|
|
77379
77469
|
var extname = path2.extname;
|
|
77380
|
-
var
|
|
77470
|
+
var join17 = path2.join;
|
|
77381
77471
|
var resolve5 = path2.resolve;
|
|
77382
77472
|
module2.exports = View;
|
|
77383
77473
|
function View(name, options) {
|
|
@@ -77439,12 +77529,12 @@ var require_view = __commonJS({
|
|
|
77439
77529
|
};
|
|
77440
77530
|
View.prototype.resolve = function resolve6(dir, file2) {
|
|
77441
77531
|
var ext = this.ext;
|
|
77442
|
-
var path3 =
|
|
77532
|
+
var path3 = join17(dir, file2);
|
|
77443
77533
|
var stat = tryStat(path3);
|
|
77444
77534
|
if (stat && stat.isFile()) {
|
|
77445
77535
|
return path3;
|
|
77446
77536
|
}
|
|
77447
|
-
path3 =
|
|
77537
|
+
path3 = join17(dir, basename4(file2, ext), "index" + ext);
|
|
77448
77538
|
stat = tryStat(path3);
|
|
77449
77539
|
if (stat && stat.isFile()) {
|
|
77450
77540
|
return path3;
|
|
@@ -81089,7 +81179,7 @@ var require_send = __commonJS({
|
|
|
81089
81179
|
var Stream = require("stream");
|
|
81090
81180
|
var util2 = require("util");
|
|
81091
81181
|
var extname = path2.extname;
|
|
81092
|
-
var
|
|
81182
|
+
var join17 = path2.join;
|
|
81093
81183
|
var normalize = path2.normalize;
|
|
81094
81184
|
var resolve5 = path2.resolve;
|
|
81095
81185
|
var sep2 = path2.sep;
|
|
@@ -81261,7 +81351,7 @@ var require_send = __commonJS({
|
|
|
81261
81351
|
return res;
|
|
81262
81352
|
}
|
|
81263
81353
|
parts = path3.split(sep2);
|
|
81264
|
-
path3 = normalize(
|
|
81354
|
+
path3 = normalize(join17(root, path3));
|
|
81265
81355
|
} else {
|
|
81266
81356
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
81267
81357
|
debug('malicious path "%s"', path3);
|
|
@@ -81394,7 +81484,7 @@ var require_send = __commonJS({
|
|
|
81394
81484
|
if (err2) return self.onStatError(err2);
|
|
81395
81485
|
return self.error(404);
|
|
81396
81486
|
}
|
|
81397
|
-
var p =
|
|
81487
|
+
var p = join17(path3, self._index[i]);
|
|
81398
81488
|
debug('stat "%s"', p);
|
|
81399
81489
|
fs.stat(p, function(err3, stat) {
|
|
81400
81490
|
if (err3) return next(err3);
|
|
@@ -85105,10 +85195,345 @@ var init_skills = __esm({
|
|
|
85105
85195
|
}
|
|
85106
85196
|
});
|
|
85107
85197
|
|
|
85198
|
+
// src/commands/replay-calibrate.ts
|
|
85199
|
+
var replay_calibrate_exports = {};
|
|
85200
|
+
__export(replay_calibrate_exports, {
|
|
85201
|
+
RUNTIMES: () => RUNTIMES,
|
|
85202
|
+
calibrate: () => calibrate,
|
|
85203
|
+
calibrateTask: () => calibrateTask,
|
|
85204
|
+
environmentOf: () => environmentOf,
|
|
85205
|
+
parseCalibrateArgs: () => parseCalibrateArgs,
|
|
85206
|
+
runCalibration: () => runCalibration,
|
|
85207
|
+
runnerOf: () => runnerOf
|
|
85208
|
+
});
|
|
85209
|
+
function parseCalibrateArgs(argv) {
|
|
85210
|
+
const options = {
|
|
85211
|
+
setupCmd: null,
|
|
85212
|
+
testCmd: "",
|
|
85213
|
+
repoCommands: false,
|
|
85214
|
+
runs: 2,
|
|
85215
|
+
force: false,
|
|
85216
|
+
limit: null,
|
|
85217
|
+
timeoutMs: 30 * 6e4,
|
|
85218
|
+
runRef: null,
|
|
85219
|
+
dryRun: false
|
|
85220
|
+
};
|
|
85221
|
+
const errors = [];
|
|
85222
|
+
for (let i = 0; i < argv.length; i++) {
|
|
85223
|
+
const a = argv[i];
|
|
85224
|
+
const next = () => argv[++i] ?? "";
|
|
85225
|
+
if (a === "--test-cmd") options.testCmd = next();
|
|
85226
|
+
else if (a === "--setup-cmd") options.setupCmd = next() || null;
|
|
85227
|
+
else if (a === "--repo-commands") options.repoCommands = true;
|
|
85228
|
+
else if (a === "--runs") options.runs = Number(next());
|
|
85229
|
+
else if (a === "--force") options.force = true;
|
|
85230
|
+
else if (a === "--limit") options.limit = Number(next());
|
|
85231
|
+
else if (a === "--timeout-min") options.timeoutMs = Number(next()) * 6e4;
|
|
85232
|
+
else if (a === "--run-ref") options.runRef = next();
|
|
85233
|
+
else if (a === "--dry-run") options.dryRun = true;
|
|
85234
|
+
else errors.push(`Unknown option: ${a}`);
|
|
85235
|
+
}
|
|
85236
|
+
if (!options.testCmd && !options.repoCommands)
|
|
85237
|
+
errors.push(
|
|
85238
|
+
"--test-cmd is required (or --repo-commands, to use the repository's from Haansi)."
|
|
85239
|
+
);
|
|
85240
|
+
if (!Number.isInteger(options.runs) || options.runs < 1 || options.runs > 5)
|
|
85241
|
+
errors.push("--runs must be 1 to 5.");
|
|
85242
|
+
if (options.limit !== null && (!Number.isInteger(options.limit) || options.limit < 1))
|
|
85243
|
+
errors.push("--limit must be a positive whole number.");
|
|
85244
|
+
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)
|
|
85245
|
+
errors.push("--timeout-min must be a positive number.");
|
|
85246
|
+
return { options, errors };
|
|
85247
|
+
}
|
|
85248
|
+
function runnerOf(env) {
|
|
85249
|
+
if (env.ImageOS && env.ImageVersion)
|
|
85250
|
+
return `${env.ImageOS}-${env.ImageVersion}`;
|
|
85251
|
+
if (env.RUNNER_OS)
|
|
85252
|
+
return [env.RUNNER_OS, env.RUNNER_ARCH].filter(Boolean).join("/");
|
|
85253
|
+
return null;
|
|
85254
|
+
}
|
|
85255
|
+
async function environmentOf(deps, commands) {
|
|
85256
|
+
const runtimes = {};
|
|
85257
|
+
for (const name of RUNTIMES) {
|
|
85258
|
+
const out = await deps.shell(
|
|
85259
|
+
`command -v ${name} >/dev/null 2>&1 && ${name} --version 2>&1 | head -n 1`,
|
|
85260
|
+
deps.repoDir,
|
|
85261
|
+
{},
|
|
85262
|
+
3e4
|
|
85263
|
+
);
|
|
85264
|
+
const version3 = out.output.trim().split("\n")[0]?.trim();
|
|
85265
|
+
if (out.code === 0 && version3) runtimes[name] = version3.slice(0, 200);
|
|
85266
|
+
}
|
|
85267
|
+
const described = {
|
|
85268
|
+
setupCommand: commands.setupCmd,
|
|
85269
|
+
testCommand: commands.testCmd,
|
|
85270
|
+
runtimes,
|
|
85271
|
+
runner: runnerOf(deps.env),
|
|
85272
|
+
cliVersion: deps.cliVersion
|
|
85273
|
+
};
|
|
85274
|
+
return {
|
|
85275
|
+
fingerprint: (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(described)).digest("hex"),
|
|
85276
|
+
...described
|
|
85277
|
+
};
|
|
85278
|
+
}
|
|
85279
|
+
function verdict(codes) {
|
|
85280
|
+
if (codes.every((c) => c === 0)) return "pass";
|
|
85281
|
+
if (codes.every((c) => c !== 0)) return "fail";
|
|
85282
|
+
return "flaky";
|
|
85283
|
+
}
|
|
85284
|
+
async function calibrateTask(deps, options, commands, task) {
|
|
85285
|
+
const label = task.name ?? task.taskKey;
|
|
85286
|
+
const runs = { base: [], target: [] };
|
|
85287
|
+
const done = (reason) => ({
|
|
85288
|
+
taskId: task.taskId,
|
|
85289
|
+
inputsFingerprint: task.inputsFingerprint,
|
|
85290
|
+
state: STATE[reason],
|
|
85291
|
+
reason,
|
|
85292
|
+
runs
|
|
85293
|
+
});
|
|
85294
|
+
for (const side of ["base", "target"]) {
|
|
85295
|
+
const dir = (0, import_node_path9.join)(
|
|
85296
|
+
deps.workDir(),
|
|
85297
|
+
`${task.taskId.replace(/[^a-zA-Z0-9-]/g, "")}-${side}`
|
|
85298
|
+
);
|
|
85299
|
+
try {
|
|
85300
|
+
try {
|
|
85301
|
+
await cleanClone(
|
|
85302
|
+
deps,
|
|
85303
|
+
dir,
|
|
85304
|
+
side === "base" ? task.baseSha : task.targetSha,
|
|
85305
|
+
side === "base" ? task.targetSha : null
|
|
85306
|
+
);
|
|
85307
|
+
} catch (err2) {
|
|
85308
|
+
deps.log(
|
|
85309
|
+
`\u2026 ${label}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
85310
|
+
);
|
|
85311
|
+
return done("checkout_failed");
|
|
85312
|
+
}
|
|
85313
|
+
if (commands.setupCmd) {
|
|
85314
|
+
const setup = await deps.shell(
|
|
85315
|
+
commands.setupCmd,
|
|
85316
|
+
dir,
|
|
85317
|
+
{},
|
|
85318
|
+
options.timeoutMs
|
|
85319
|
+
);
|
|
85320
|
+
if (setup.code !== 0) {
|
|
85321
|
+
deps.log(
|
|
85322
|
+
`\u2026 ${label} [${side}]: setup failed: ${setup.output.trim().split("\n").slice(-3).join(" | ")}`
|
|
85323
|
+
);
|
|
85324
|
+
return done("setup_failed");
|
|
85325
|
+
}
|
|
85326
|
+
}
|
|
85327
|
+
if (side === "base") {
|
|
85328
|
+
for (const f of task.testFiles) {
|
|
85329
|
+
const content = await deps.showFile(task.targetSha, f);
|
|
85330
|
+
if (content === null) deps.removeFile((0, import_node_path9.join)(dir, f));
|
|
85331
|
+
else deps.writeFile((0, import_node_path9.join)(dir, f), content);
|
|
85332
|
+
}
|
|
85333
|
+
}
|
|
85334
|
+
for (let i = 0; i < options.runs; i++) {
|
|
85335
|
+
const test = await deps.shell(
|
|
85336
|
+
commands.testCmd,
|
|
85337
|
+
dir,
|
|
85338
|
+
{ HAANSI_REPLAY_TEST_FILES: task.testFiles.join(" ") },
|
|
85339
|
+
options.timeoutMs
|
|
85340
|
+
);
|
|
85341
|
+
runs[side].push(test.code);
|
|
85342
|
+
if (side === "target" && test.code !== 0)
|
|
85343
|
+
for (const line of test.output.trim().split("\n").slice(-FAILURE_LINES))
|
|
85344
|
+
deps.log(` ${line}`);
|
|
85345
|
+
}
|
|
85346
|
+
} finally {
|
|
85347
|
+
deps.removeDir(dir);
|
|
85348
|
+
}
|
|
85349
|
+
const seen = verdict(runs[side]);
|
|
85350
|
+
if (side === "base") {
|
|
85351
|
+
if (seen === "flaky") return done("flaky_at_base");
|
|
85352
|
+
if (seen === "pass") return done("passes_at_base");
|
|
85353
|
+
} else {
|
|
85354
|
+
if (seen === "flaky") return done("flaky_at_target");
|
|
85355
|
+
if (seen === "fail") return done("fails_at_target");
|
|
85356
|
+
}
|
|
85357
|
+
}
|
|
85358
|
+
return done("fails_at_base_passes_at_target");
|
|
85359
|
+
}
|
|
85360
|
+
async function runCalibration(options, deps) {
|
|
85361
|
+
const remote = await deps.git(["remote", "get-url", "origin"], deps.repoDir);
|
|
85362
|
+
const repository = remote.code === 0 ? repositoryFromRemote(remote.output) : null;
|
|
85363
|
+
if (!repository)
|
|
85364
|
+
throw new Error("Run from a clone of the repository (no origin remote).");
|
|
85365
|
+
const set2 = await deps.getReplaySet(repository);
|
|
85366
|
+
const repo = options.repoCommands ? set2.repoCommands : null;
|
|
85367
|
+
const setupCmd = options.setupCmd ?? repo?.setupCommand ?? null;
|
|
85368
|
+
const testCmd = options.testCmd || repo?.testCommand || "";
|
|
85369
|
+
if (!testCmd)
|
|
85370
|
+
throw new Error(
|
|
85371
|
+
`No test command: pass --test-cmd, or set one for ${repository} in Haansi.`
|
|
85372
|
+
);
|
|
85373
|
+
const environment = await environmentOf(deps, { setupCmd, testCmd });
|
|
85374
|
+
const calibrations = [];
|
|
85375
|
+
let reused = 0;
|
|
85376
|
+
let skipped = 0;
|
|
85377
|
+
const tasks = options.limit ? set2.tasks.slice(0, options.limit) : set2.tasks;
|
|
85378
|
+
for (const task of tasks) {
|
|
85379
|
+
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
|
+
if (!SHA.test(task.baseSha) || !SHA.test(task.targetSha)) {
|
|
85387
|
+
skipped += 1;
|
|
85388
|
+
deps.log(`skip ${label}: its commits are not commit ids`);
|
|
85389
|
+
continue;
|
|
85390
|
+
}
|
|
85391
|
+
if (task.testFiles.length === 0) {
|
|
85392
|
+
skipped += 1;
|
|
85393
|
+
deps.log(
|
|
85394
|
+
`skip ${label}: graded by checks (calibrating by checks comes later)`
|
|
85395
|
+
);
|
|
85396
|
+
continue;
|
|
85397
|
+
}
|
|
85398
|
+
if (!task.testFiles.every(safeTestFile)) {
|
|
85399
|
+
skipped += 1;
|
|
85400
|
+
deps.log(`skip ${label}: a test path is not usable`);
|
|
85401
|
+
continue;
|
|
85402
|
+
}
|
|
85403
|
+
if (!await ensureCommit(deps, task.baseSha) || !await ensureCommit(deps, task.targetSha)) {
|
|
85404
|
+
skipped += 1;
|
|
85405
|
+
deps.log(`skip ${label}: its commits are not reachable from origin`);
|
|
85406
|
+
continue;
|
|
85407
|
+
}
|
|
85408
|
+
const result = await calibrateTask(
|
|
85409
|
+
deps,
|
|
85410
|
+
options,
|
|
85411
|
+
{ setupCmd, testCmd },
|
|
85412
|
+
task
|
|
85413
|
+
);
|
|
85414
|
+
calibrations.push(result);
|
|
85415
|
+
deps.log(
|
|
85416
|
+
`${result.state.padEnd(7)} ${label}: ${result.reason.replace(/_/g, " ")} (base ${result.runs.base.join(",") || "-"}; target ${result.runs.target.join(",") || "-"})`
|
|
85417
|
+
);
|
|
85418
|
+
}
|
|
85419
|
+
if (options.dryRun || calibrations.length === 0)
|
|
85420
|
+
return { calibrations, reused, skipped, recorded: null };
|
|
85421
|
+
let recorded = 0;
|
|
85422
|
+
for (let i = 0; i < calibrations.length; i += REPORT_CHUNK) {
|
|
85423
|
+
const out = await deps.report({
|
|
85424
|
+
runRef: options.runRef,
|
|
85425
|
+
environment,
|
|
85426
|
+
results: calibrations.slice(i, i + REPORT_CHUNK)
|
|
85427
|
+
});
|
|
85428
|
+
recorded += out.recorded;
|
|
85429
|
+
if (out.refused.length)
|
|
85430
|
+
deps.log(
|
|
85431
|
+
`Haansi did not record ${out.refused.length} calibration(s): their task changed since it was listed.`
|
|
85432
|
+
);
|
|
85433
|
+
}
|
|
85434
|
+
return { calibrations, reused, skipped, recorded };
|
|
85435
|
+
}
|
|
85436
|
+
async function calibrate(argv) {
|
|
85437
|
+
if (argv.includes("--help")) {
|
|
85438
|
+
console.log(USAGE);
|
|
85439
|
+
return;
|
|
85440
|
+
}
|
|
85441
|
+
const { options, errors } = parseCalibrateArgs(argv);
|
|
85442
|
+
if (errors.length) {
|
|
85443
|
+
for (const e of errors) console.error(e);
|
|
85444
|
+
console.log(USAGE);
|
|
85445
|
+
process.exitCode = 1;
|
|
85446
|
+
return;
|
|
85447
|
+
}
|
|
85448
|
+
options.runRef ??= githubRunRef(process.env);
|
|
85449
|
+
const token = resolveToken5();
|
|
85450
|
+
const apiUrl2 = apiUrl();
|
|
85451
|
+
const work = (0, import_node_fs10.mkdtempSync)((0, import_node_path9.join)((0, import_node_os9.tmpdir)(), "haansi-calibrate-"));
|
|
85452
|
+
try {
|
|
85453
|
+
const { calibrations, reused, skipped, recorded } = await runCalibration(
|
|
85454
|
+
options,
|
|
85455
|
+
{
|
|
85456
|
+
...localDeps(work),
|
|
85457
|
+
getReplaySet: (repository) => apiGet3(
|
|
85458
|
+
`/memory/replays/tasks?repository=${encodeURIComponent(repository)}`,
|
|
85459
|
+
token,
|
|
85460
|
+
apiUrl2
|
|
85461
|
+
),
|
|
85462
|
+
report: async (body) => await apiPost2(
|
|
85463
|
+
"/memory/replays/tasks/calibration",
|
|
85464
|
+
body,
|
|
85465
|
+
token,
|
|
85466
|
+
apiUrl2
|
|
85467
|
+
),
|
|
85468
|
+
env: process.env,
|
|
85469
|
+
cliVersion: cliVersion()
|
|
85470
|
+
}
|
|
85471
|
+
);
|
|
85472
|
+
const count = (state) => calibrations.filter((c) => c.state === state).length;
|
|
85473
|
+
console.log(
|
|
85474
|
+
`
|
|
85475
|
+
Valid: ${count("valid")} \xB7 Invalid: ${count("invalid")} \xB7 Flaky: ${count("flaky")} \xB7 Errors: ${count("error")} \xB7 ${reused} already calibrated \xB7 ${skipped} skipped`
|
|
85476
|
+
);
|
|
85477
|
+
console.log(
|
|
85478
|
+
recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} calibration(s) to Haansi.`
|
|
85479
|
+
);
|
|
85480
|
+
} catch (err2) {
|
|
85481
|
+
console.error(err2 instanceof Error ? err2.message : String(err2));
|
|
85482
|
+
process.exitCode = 1;
|
|
85483
|
+
} finally {
|
|
85484
|
+
(0, import_node_fs10.rmSync)(work, { recursive: true, force: true });
|
|
85485
|
+
}
|
|
85486
|
+
}
|
|
85487
|
+
function cliVersion() {
|
|
85488
|
+
try {
|
|
85489
|
+
return String(require_package().version);
|
|
85490
|
+
} catch {
|
|
85491
|
+
return null;
|
|
85492
|
+
}
|
|
85493
|
+
}
|
|
85494
|
+
var import_node_crypto2, import_node_fs10, import_node_os9, import_node_path9, RUNTIMES, REPORT_CHUNK, FAILURE_LINES, STATE, USAGE;
|
|
85495
|
+
var init_replay_calibrate = __esm({
|
|
85496
|
+
"src/commands/replay-calibrate.ts"() {
|
|
85497
|
+
"use strict";
|
|
85498
|
+
import_node_crypto2 = require("node:crypto");
|
|
85499
|
+
import_node_fs10 = require("node:fs");
|
|
85500
|
+
import_node_os9 = require("node:os");
|
|
85501
|
+
import_node_path9 = require("node:path");
|
|
85502
|
+
init_paths();
|
|
85503
|
+
init_api2();
|
|
85504
|
+
init_replay();
|
|
85505
|
+
RUNTIMES = ["node", "python3", "go", "java", "ruby", "rustc"];
|
|
85506
|
+
REPORT_CHUNK = 100;
|
|
85507
|
+
FAILURE_LINES = 5;
|
|
85508
|
+
STATE = {
|
|
85509
|
+
fails_at_base_passes_at_target: "valid",
|
|
85510
|
+
passes_at_base: "invalid",
|
|
85511
|
+
fails_at_target: "invalid",
|
|
85512
|
+
flaky_at_base: "flaky",
|
|
85513
|
+
flaky_at_target: "flaky",
|
|
85514
|
+
setup_failed: "error",
|
|
85515
|
+
checkout_failed: "error"
|
|
85516
|
+
};
|
|
85517
|
+
USAGE = `Usage: haansi replay calibrate --test-cmd "<cmd>" [--setup-cmd "<cmd>"] [--repo-commands]
|
|
85518
|
+
[--runs 2] [--force] [--limit n] [--timeout-min 30]
|
|
85519
|
+
[--run-ref <url>] [--dry-run]
|
|
85520
|
+
|
|
85521
|
+
Checks each task of this repository's replay set with no agent: the tests of
|
|
85522
|
+
its target must fail at its base and pass at its target, --runs times each.
|
|
85523
|
+
Already calibrated tasks are not run again until their inputs or the
|
|
85524
|
+
environment (commands, runtimes, runner, CLI version) change; --force runs
|
|
85525
|
+
them all.`;
|
|
85526
|
+
}
|
|
85527
|
+
});
|
|
85528
|
+
|
|
85108
85529
|
// src/commands/replay.ts
|
|
85109
85530
|
var replay_exports = {};
|
|
85110
85531
|
__export(replay_exports, {
|
|
85532
|
+
SHA: () => SHA,
|
|
85533
|
+
cleanClone: () => cleanClone,
|
|
85534
|
+
ensureCommit: () => ensureCommit,
|
|
85111
85535
|
githubRunRef: () => githubRunRef,
|
|
85536
|
+
localDeps: () => localDeps,
|
|
85112
85537
|
parseReplayArgs: () => parseReplayArgs,
|
|
85113
85538
|
replay: () => replay,
|
|
85114
85539
|
replayArm: () => replayArm,
|
|
@@ -85238,7 +85663,7 @@ async function ensureCommit(deps, sha) {
|
|
|
85238
85663
|
return true;
|
|
85239
85664
|
return (await deps.git(["fetch", "--quiet", "origin", sha], deps.repoDir)).code === 0;
|
|
85240
85665
|
}
|
|
85241
|
-
async function
|
|
85666
|
+
async function cleanClone(deps, dir, sha, unreachable) {
|
|
85242
85667
|
const steps = [
|
|
85243
85668
|
[["init", "--quiet", dir], deps.repoDir],
|
|
85244
85669
|
[
|
|
@@ -85248,7 +85673,7 @@ async function cleanCheckout(deps, dir, task) {
|
|
|
85248
85673
|
"--no-tags",
|
|
85249
85674
|
"--no-write-fetch-head",
|
|
85250
85675
|
deps.repoDir,
|
|
85251
|
-
`${
|
|
85676
|
+
`${sha}:refs/heads/${REPLAY_BRANCH}`
|
|
85252
85677
|
],
|
|
85253
85678
|
dir
|
|
85254
85679
|
],
|
|
@@ -85256,18 +85681,22 @@ async function cleanCheckout(deps, dir, task) {
|
|
|
85256
85681
|
];
|
|
85257
85682
|
for (const [args, cwd] of steps) {
|
|
85258
85683
|
if ((await deps.git(args, cwd)).code !== 0)
|
|
85259
|
-
throw new Error(`could not check out ${
|
|
85684
|
+
throw new Error(`could not check out ${sha}`);
|
|
85260
85685
|
}
|
|
85686
|
+
if (unreachable === null) return;
|
|
85261
85687
|
const leak = await deps.git(
|
|
85262
|
-
["cat-file", "-e", `${
|
|
85688
|
+
["cat-file", "-e", `${unreachable}^{commit}`],
|
|
85263
85689
|
dir
|
|
85264
85690
|
);
|
|
85265
85691
|
if (leak.code === 0)
|
|
85266
85692
|
throw new Error("the fix's commit is readable in the clone: run refused");
|
|
85267
85693
|
}
|
|
85694
|
+
function cleanCheckout(deps, dir, task) {
|
|
85695
|
+
return cleanClone(deps, dir, task.baseSha, task.targetSha);
|
|
85696
|
+
}
|
|
85268
85697
|
async function replayArm(deps, options, task, arm, playbook) {
|
|
85269
85698
|
const started = deps.now();
|
|
85270
|
-
const dir = (0,
|
|
85699
|
+
const dir = (0, import_node_path10.join)(
|
|
85271
85700
|
deps.workDir(),
|
|
85272
85701
|
`${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
|
|
85273
85702
|
);
|
|
@@ -85293,10 +85722,10 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85293
85722
|
}
|
|
85294
85723
|
deps.log(`\u2026 ${label}: set up in ${since(at)}`);
|
|
85295
85724
|
}
|
|
85296
|
-
const promptFile = (0,
|
|
85725
|
+
const promptFile = (0, import_node_path10.join)(dir, ".haansi-replay", "prompt.md");
|
|
85297
85726
|
if (arm === "haansi" && playbook)
|
|
85298
85727
|
deps.writeFile(
|
|
85299
|
-
(0,
|
|
85728
|
+
(0, import_node_path10.join)(dir, ".haansi-replay", "playbook.md"),
|
|
85300
85729
|
playbook.skillMd
|
|
85301
85730
|
);
|
|
85302
85731
|
let feedback = null;
|
|
@@ -85313,11 +85742,11 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85313
85742
|
{ HAANSI_REPLAY_PROMPT: prompt2, HAANSI_REPLAY_PROMPT_FILE: promptFile },
|
|
85314
85743
|
options.timeoutMs
|
|
85315
85744
|
);
|
|
85316
|
-
const own = new Map(files.map((f) => [f, deps.readFile((0,
|
|
85745
|
+
const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path10.join)(dir, f))]));
|
|
85317
85746
|
for (const f of files) {
|
|
85318
85747
|
const content = await deps.showFile(task.targetSha, f);
|
|
85319
|
-
if (content === null) deps.removeFile((0,
|
|
85320
|
-
else deps.writeFile((0,
|
|
85748
|
+
if (content === null) deps.removeFile((0, import_node_path10.join)(dir, f));
|
|
85749
|
+
else deps.writeFile((0, import_node_path10.join)(dir, f), content);
|
|
85321
85750
|
}
|
|
85322
85751
|
const testAt = deps.now();
|
|
85323
85752
|
deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
|
|
@@ -85331,7 +85760,7 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85331
85760
|
`\u2026 ${label}: tests ${test.code === 0 ? "passed" : "failed"} in ${since(testAt)}`
|
|
85332
85761
|
);
|
|
85333
85762
|
if (test.code !== 0) {
|
|
85334
|
-
for (const line of test.output.trim().split("\n").slice(-
|
|
85763
|
+
for (const line of test.output.trim().split("\n").slice(-FAILURE_LINES2))
|
|
85335
85764
|
deps.log(` ${line}`);
|
|
85336
85765
|
}
|
|
85337
85766
|
if (test.code === 0) {
|
|
@@ -85346,8 +85775,8 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85346
85775
|
};
|
|
85347
85776
|
}
|
|
85348
85777
|
for (const [file2, content] of own) {
|
|
85349
|
-
if (content === null) deps.removeFile((0,
|
|
85350
|
-
else deps.writeFile((0,
|
|
85778
|
+
if (content === null) deps.removeFile((0, import_node_path10.join)(dir, file2));
|
|
85779
|
+
else deps.writeFile((0, import_node_path10.join)(dir, file2), content);
|
|
85351
85780
|
}
|
|
85352
85781
|
feedback = test.output.slice(-FEEDBACK_CHARS);
|
|
85353
85782
|
}
|
|
@@ -85477,17 +85906,50 @@ function runCommand(file2, args, cwd, env, timeoutMs, stdoutOnly = false) {
|
|
|
85477
85906
|
});
|
|
85478
85907
|
});
|
|
85479
85908
|
}
|
|
85909
|
+
function localDeps(work) {
|
|
85910
|
+
return {
|
|
85911
|
+
git: (args, cwd) => runCommand("git", args, cwd, {}, 10 * 6e4),
|
|
85912
|
+
showFile: async (sha, path2) => {
|
|
85913
|
+
const shown = await runCommand(
|
|
85914
|
+
"git",
|
|
85915
|
+
["show", `${sha}:${path2}`],
|
|
85916
|
+
process.cwd(),
|
|
85917
|
+
{},
|
|
85918
|
+
6e4,
|
|
85919
|
+
true
|
|
85920
|
+
);
|
|
85921
|
+
return shown.code === 0 ? shown.output : null;
|
|
85922
|
+
},
|
|
85923
|
+
shell: (command2, cwd, env, timeoutMs) => runCommand("sh", ["-c", command2], cwd, env, timeoutMs),
|
|
85924
|
+
repoDir: process.cwd(),
|
|
85925
|
+
workDir: () => work,
|
|
85926
|
+
writeFile: (path2, content) => {
|
|
85927
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(path2), { recursive: true });
|
|
85928
|
+
(0, import_node_fs11.writeFileSync)(path2, content);
|
|
85929
|
+
},
|
|
85930
|
+
readFile: (path2) => (0, import_node_fs11.existsSync)(path2) ? (0, import_node_fs11.readFileSync)(path2, "utf-8") : null,
|
|
85931
|
+
removeFile: (path2) => (0, import_node_fs11.rmSync)(path2, { force: true }),
|
|
85932
|
+
removeDir: (path2) => (0, import_node_fs11.rmSync)(path2, { recursive: true, force: true }),
|
|
85933
|
+
now: () => Date.now(),
|
|
85934
|
+
log: (line) => console.log(line)
|
|
85935
|
+
};
|
|
85936
|
+
}
|
|
85480
85937
|
async function replay(argv) {
|
|
85938
|
+
if (argv[0] === "calibrate") {
|
|
85939
|
+
const { calibrate: calibrate2 } = await Promise.resolve().then(() => (init_replay_calibrate(), replay_calibrate_exports));
|
|
85940
|
+
await calibrate2(argv.slice(1));
|
|
85941
|
+
return;
|
|
85942
|
+
}
|
|
85481
85943
|
const { sub, options, errors } = parseReplayArgs(argv);
|
|
85482
85944
|
if (sub !== "run" || errors.length) {
|
|
85483
85945
|
for (const e of sub === "run" ? errors : []) console.error(e);
|
|
85484
|
-
console.log(
|
|
85946
|
+
console.log(USAGE2);
|
|
85485
85947
|
if (sub === "run") process.exitCode = 1;
|
|
85486
85948
|
return;
|
|
85487
85949
|
}
|
|
85488
85950
|
const token = resolveToken5();
|
|
85489
85951
|
const apiUrl2 = apiUrl();
|
|
85490
|
-
const work = (0,
|
|
85952
|
+
const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
|
|
85491
85953
|
try {
|
|
85492
85954
|
const { results, skipped, recorded } = await runReplays(options, {
|
|
85493
85955
|
getTasks: async (familyId, cutoff) => (await apiGet3(
|
|
@@ -85506,30 +85968,7 @@ async function replay(argv) {
|
|
|
85506
85968
|
token,
|
|
85507
85969
|
apiUrl2
|
|
85508
85970
|
),
|
|
85509
|
-
|
|
85510
|
-
showFile: async (sha, path2) => {
|
|
85511
|
-
const shown = await runCommand(
|
|
85512
|
-
"git",
|
|
85513
|
-
["show", `${sha}:${path2}`],
|
|
85514
|
-
process.cwd(),
|
|
85515
|
-
{},
|
|
85516
|
-
6e4,
|
|
85517
|
-
true
|
|
85518
|
-
);
|
|
85519
|
-
return shown.code === 0 ? shown.output : null;
|
|
85520
|
-
},
|
|
85521
|
-
shell: (command2, cwd, env, timeoutMs) => runCommand("sh", ["-c", command2], cwd, env, timeoutMs),
|
|
85522
|
-
repoDir: process.cwd(),
|
|
85523
|
-
workDir: () => work,
|
|
85524
|
-
writeFile: (path2, content) => {
|
|
85525
|
-
(0, import_node_fs10.mkdirSync)((0, import_node_path9.dirname)(path2), { recursive: true });
|
|
85526
|
-
(0, import_node_fs10.writeFileSync)(path2, content);
|
|
85527
|
-
},
|
|
85528
|
-
readFile: (path2) => (0, import_node_fs10.existsSync)(path2) ? (0, import_node_fs10.readFileSync)(path2, "utf-8") : null,
|
|
85529
|
-
removeFile: (path2) => (0, import_node_fs10.rmSync)(path2, { force: true }),
|
|
85530
|
-
removeDir: (path2) => (0, import_node_fs10.rmSync)(path2, { recursive: true, force: true }),
|
|
85531
|
-
now: () => Date.now(),
|
|
85532
|
-
log: (line) => console.log(line)
|
|
85971
|
+
...localDeps(work)
|
|
85533
85972
|
});
|
|
85534
85973
|
const passed = (arm) => results.filter((r) => r.arm === arm && r.passed).length;
|
|
85535
85974
|
const ran = (arm) => results.filter((r) => r.arm === arm).length;
|
|
@@ -85541,30 +85980,32 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
|
|
|
85541
85980
|
recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} result(s) to Haansi.`
|
|
85542
85981
|
);
|
|
85543
85982
|
} finally {
|
|
85544
|
-
(0,
|
|
85983
|
+
(0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
|
|
85545
85984
|
}
|
|
85546
85985
|
}
|
|
85547
|
-
var import_node_child_process4,
|
|
85986
|
+
var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path10, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
|
|
85548
85987
|
var init_replay = __esm({
|
|
85549
85988
|
"src/commands/replay.ts"() {
|
|
85550
85989
|
"use strict";
|
|
85551
85990
|
import_node_child_process4 = require("node:child_process");
|
|
85552
|
-
|
|
85553
|
-
|
|
85554
|
-
|
|
85991
|
+
import_node_fs11 = require("node:fs");
|
|
85992
|
+
import_node_os10 = require("node:os");
|
|
85993
|
+
import_node_path10 = require("node:path");
|
|
85555
85994
|
init_paths();
|
|
85556
85995
|
init_api2();
|
|
85557
85996
|
SHA = /^[0-9a-f]{7,40}$/i;
|
|
85558
85997
|
ARMS = ["baseline", "haansi"];
|
|
85559
85998
|
FEEDBACK_CHARS = 6e3;
|
|
85560
|
-
|
|
85999
|
+
FAILURE_LINES2 = 8;
|
|
85561
86000
|
REPLAY_BRANCH = "replay";
|
|
85562
|
-
|
|
86001
|
+
USAGE2 = `Usage: haansi replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
|
|
85563
86002
|
[--setup-cmd "<cmd>"] [--attempts 3] [--arms baseline,haansi] [--limit n]
|
|
85564
86003
|
[--cutoff <date>] [--timeout-min 30]
|
|
85565
86004
|
[--agent-name <name>] [--model <name>] [--run-ref <url>] [--dry-run]
|
|
85566
86005
|
[--repo-commands]
|
|
85567
86006
|
|
|
86007
|
+
haansi replay calibrate --test-cmd "<cmd>" [--setup-cmd "<cmd>"] (see --help)
|
|
86008
|
+
|
|
85568
86009
|
--repo-commands uses the setup and test commands an org admin set for the
|
|
85569
86010
|
repository in Haansi (Settings \u2192 Data sources) for any not given here.
|
|
85570
86011
|
|
|
@@ -85793,6 +86234,9 @@ Commands:
|
|
|
85793
86234
|
replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
|
|
85794
86235
|
Replay a problem family's tasks in your CI, with and
|
|
85795
86236
|
without its playbook, and report the results
|
|
86237
|
+
replay calibrate --test-cmd "<cmd>"
|
|
86238
|
+
Check this repository's replay tasks are valid: their
|
|
86239
|
+
tests fail at the start and pass at the fix
|
|
85796
86240
|
skills sync [--agent <sel>] [--project] [--dir <path>]
|
|
85797
86241
|
Copy your team's published skills into Claude Code / Codex
|
|
85798
86242
|
|