haansi 0.1.52 → 0.1.54
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 +159 -20
- 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.54",
|
|
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,7 +85117,8 @@ __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 = {
|
|
@@ -85065,6 +85126,7 @@ function parseReplayArgs(argv) {
|
|
|
85065
85126
|
agentCmd: "",
|
|
85066
85127
|
testCmd: "",
|
|
85067
85128
|
setupCmd: null,
|
|
85129
|
+
repoCommands: false,
|
|
85068
85130
|
attempts: 3,
|
|
85069
85131
|
arms: [...ARMS],
|
|
85070
85132
|
limit: null,
|
|
@@ -85092,6 +85154,7 @@ function parseReplayArgs(argv) {
|
|
|
85092
85154
|
else if (a === "--model") options.model = next();
|
|
85093
85155
|
else if (a === "--run-ref") options.runRef = next();
|
|
85094
85156
|
else if (a === "--dry-run") options.dryRun = true;
|
|
85157
|
+
else if (a === "--repo-commands") options.repoCommands = true;
|
|
85095
85158
|
else if (a === "--arms")
|
|
85096
85159
|
options.arms = next().split(",").map((s) => s.trim()).filter(Boolean);
|
|
85097
85160
|
else if (!sub && !a.startsWith("-")) sub = a;
|
|
@@ -85100,7 +85163,10 @@ function parseReplayArgs(argv) {
|
|
|
85100
85163
|
if (!/^[0-9a-f-]{36}$/i.test(options.familyId))
|
|
85101
85164
|
errors.push("--family <id> is required (the family's id).");
|
|
85102
85165
|
if (!options.agentCmd) errors.push("--agent-cmd is required.");
|
|
85103
|
-
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
|
+
);
|
|
85104
85170
|
if (!Number.isInteger(options.attempts) || options.attempts < 1 || options.attempts > 20)
|
|
85105
85171
|
errors.push("--attempts must be 1 to 20.");
|
|
85106
85172
|
if (options.limit !== null && (!Number.isInteger(options.limit) || options.limit < 1))
|
|
@@ -85124,6 +85190,16 @@ function repositoryFromRemote(remote) {
|
|
|
85124
85190
|
function safeTestFile(path2) {
|
|
85125
85191
|
return path2.length > 0 && !path2.startsWith("/") && !path2.startsWith("-") && !path2.split("/").includes("..") && !/[\u0000-\u001f]/.test(path2);
|
|
85126
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
|
+
}
|
|
85127
85203
|
function skipReason(task, repository) {
|
|
85128
85204
|
if (!repository || task.repository.toLowerCase() !== repository)
|
|
85129
85205
|
return `in ${task.repository}, not this repository`;
|
|
@@ -85162,6 +85238,33 @@ async function ensureCommit(deps, sha) {
|
|
|
85162
85238
|
return true;
|
|
85163
85239
|
return (await deps.git(["fetch", "--quiet", "origin", sha], deps.repoDir)).code === 0;
|
|
85164
85240
|
}
|
|
85241
|
+
async function cleanCheckout(deps, dir, task) {
|
|
85242
|
+
const steps = [
|
|
85243
|
+
[["init", "--quiet", dir], deps.repoDir],
|
|
85244
|
+
[
|
|
85245
|
+
[
|
|
85246
|
+
"fetch",
|
|
85247
|
+
"--quiet",
|
|
85248
|
+
"--no-tags",
|
|
85249
|
+
"--no-write-fetch-head",
|
|
85250
|
+
deps.repoDir,
|
|
85251
|
+
`${task.baseSha}:refs/heads/${REPLAY_BRANCH}`
|
|
85252
|
+
],
|
|
85253
|
+
dir
|
|
85254
|
+
],
|
|
85255
|
+
[["checkout", "--quiet", REPLAY_BRANCH], dir]
|
|
85256
|
+
];
|
|
85257
|
+
for (const [args, cwd] of steps) {
|
|
85258
|
+
if ((await deps.git(args, cwd)).code !== 0)
|
|
85259
|
+
throw new Error(`could not check out ${task.baseSha}`);
|
|
85260
|
+
}
|
|
85261
|
+
const leak = await deps.git(
|
|
85262
|
+
["cat-file", "-e", `${task.targetSha}^{commit}`],
|
|
85263
|
+
dir
|
|
85264
|
+
);
|
|
85265
|
+
if (leak.code === 0)
|
|
85266
|
+
throw new Error("the fix's commit is readable in the clone: run refused");
|
|
85267
|
+
}
|
|
85165
85268
|
async function replayArm(deps, options, task, arm, playbook) {
|
|
85166
85269
|
const started = deps.now();
|
|
85167
85270
|
const dir = (0, import_node_path9.join)(
|
|
@@ -85169,12 +85272,8 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85169
85272
|
`${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
|
|
85170
85273
|
);
|
|
85171
85274
|
const files = task.testFiles;
|
|
85172
|
-
const add = await deps.git(
|
|
85173
|
-
["worktree", "add", "--detach", dir, task.baseSha],
|
|
85174
|
-
deps.repoDir
|
|
85175
|
-
);
|
|
85176
|
-
if (add.code !== 0) throw new Error(`could not check out ${task.baseSha}`);
|
|
85177
85275
|
try {
|
|
85276
|
+
await cleanCheckout(deps, dir, task);
|
|
85178
85277
|
const label = `${task.name ?? task.processId} [${arm}]`;
|
|
85179
85278
|
const since = (from) => `${Math.round((deps.now() - from) / 1e3)}s`;
|
|
85180
85279
|
deps.log(`\u2026 ${label}: checked out ${task.baseSha.slice(0, 9)}`);
|
|
@@ -85215,12 +85314,11 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85215
85314
|
options.timeoutMs
|
|
85216
85315
|
);
|
|
85217
85316
|
const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path9.join)(dir, f))]));
|
|
85218
|
-
const
|
|
85219
|
-
|
|
85220
|
-
dir
|
|
85221
|
-
|
|
85222
|
-
|
|
85223
|
-
throw new Error("could not check out the fix's tests");
|
|
85317
|
+
for (const f of files) {
|
|
85318
|
+
const content = await deps.showFile(task.targetSha, f);
|
|
85319
|
+
if (content === null) deps.removeFile((0, import_node_path9.join)(dir, f));
|
|
85320
|
+
else deps.writeFile((0, import_node_path9.join)(dir, f), content);
|
|
85321
|
+
}
|
|
85224
85322
|
const testAt = deps.now();
|
|
85225
85323
|
deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
|
|
85226
85324
|
const test = await deps.shell(
|
|
@@ -85263,7 +85361,6 @@ async function replayArm(deps, options, task, arm, playbook) {
|
|
|
85263
85361
|
revisionId: arm === "haansi" ? playbook?.revisionId ?? null : null
|
|
85264
85362
|
};
|
|
85265
85363
|
} finally {
|
|
85266
|
-
await deps.git(["worktree", "remove", "--force", dir], deps.repoDir);
|
|
85267
85364
|
deps.removeDir(dir);
|
|
85268
85365
|
}
|
|
85269
85366
|
}
|
|
@@ -85294,6 +85391,24 @@ async function runReplays(options, deps) {
|
|
|
85294
85391
|
const chosen = options.limit ? runnable.slice(0, options.limit) : runnable;
|
|
85295
85392
|
const results = [];
|
|
85296
85393
|
for (const task of chosen) {
|
|
85394
|
+
const commands = taskCommands(options, task);
|
|
85395
|
+
if (!commands.testCmd) {
|
|
85396
|
+
skipped += 1;
|
|
85397
|
+
deps.log(
|
|
85398
|
+
`skip ${task.name ?? task.processId}: no test command (pass --test-cmd, or set one for ${task.repository} in Haansi)`
|
|
85399
|
+
);
|
|
85400
|
+
continue;
|
|
85401
|
+
}
|
|
85402
|
+
if (commands.fromHaansi) {
|
|
85403
|
+
deps.log(
|
|
85404
|
+
`\u2026 ${task.name ?? task.processId}: using ${task.repository}'s replay commands from Haansi (setup: ${commands.setupCmd ?? "none"}; test: ${commands.testCmd})`
|
|
85405
|
+
);
|
|
85406
|
+
}
|
|
85407
|
+
const taskOptions = {
|
|
85408
|
+
...options,
|
|
85409
|
+
setupCmd: commands.setupCmd,
|
|
85410
|
+
testCmd: commands.testCmd
|
|
85411
|
+
};
|
|
85297
85412
|
if (!await ensureCommit(deps, task.baseSha) || !await ensureCommit(deps, task.targetSha)) {
|
|
85298
85413
|
skipped += 1;
|
|
85299
85414
|
deps.log(
|
|
@@ -85303,7 +85418,7 @@ async function runReplays(options, deps) {
|
|
|
85303
85418
|
}
|
|
85304
85419
|
for (const arm of arms) {
|
|
85305
85420
|
try {
|
|
85306
|
-
const result = await replayArm(deps,
|
|
85421
|
+
const result = await replayArm(deps, taskOptions, task, arm, playbook);
|
|
85307
85422
|
results.push(result);
|
|
85308
85423
|
deps.log(
|
|
85309
85424
|
`${result.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${task.name ?? task.processId} (${result.attempts}/${result.maxAttempts} attempts)`
|
|
@@ -85330,7 +85445,7 @@ async function runReplays(options, deps) {
|
|
|
85330
85445
|
);
|
|
85331
85446
|
return { results, skipped, recorded: reported.recorded };
|
|
85332
85447
|
}
|
|
85333
|
-
function runCommand(file2, args, cwd, env, timeoutMs) {
|
|
85448
|
+
function runCommand(file2, args, cwd, env, timeoutMs, stdoutOnly = false) {
|
|
85334
85449
|
return new Promise((resolvePromise) => {
|
|
85335
85450
|
const child = (0, import_node_child_process4.spawn)(file2, args, {
|
|
85336
85451
|
cwd,
|
|
@@ -85341,8 +85456,16 @@ function runCommand(file2, args, cwd, env, timeoutMs) {
|
|
|
85341
85456
|
const keep = (chunk) => {
|
|
85342
85457
|
output = (output + chunk.toString()).slice(-2e5);
|
|
85343
85458
|
};
|
|
85344
|
-
|
|
85345
|
-
|
|
85459
|
+
if (stdoutOnly) {
|
|
85460
|
+
const chunks = [];
|
|
85461
|
+
child.stdout.on("data", (chunk) => chunks.push(chunk));
|
|
85462
|
+
child.stdout.on("end", () => {
|
|
85463
|
+
output = Buffer.concat(chunks).toString();
|
|
85464
|
+
});
|
|
85465
|
+
} else {
|
|
85466
|
+
child.stdout.on("data", keep);
|
|
85467
|
+
child.stderr.on("data", keep);
|
|
85468
|
+
}
|
|
85346
85469
|
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
85347
85470
|
child.on("close", (code) => {
|
|
85348
85471
|
clearTimeout(timer);
|
|
@@ -85384,6 +85507,17 @@ async function replay(argv) {
|
|
|
85384
85507
|
apiUrl2
|
|
85385
85508
|
),
|
|
85386
85509
|
git: (args, cwd) => runCommand("git", args, cwd, {}, 10 * 6e4),
|
|
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
|
+
},
|
|
85387
85521
|
shell: (command2, cwd, env, timeoutMs) => runCommand("sh", ["-c", command2], cwd, env, timeoutMs),
|
|
85388
85522
|
repoDir: process.cwd(),
|
|
85389
85523
|
workDir: () => work,
|
|
@@ -85410,7 +85544,7 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
|
|
|
85410
85544
|
(0, import_node_fs10.rmSync)(work, { recursive: true, force: true });
|
|
85411
85545
|
}
|
|
85412
85546
|
}
|
|
85413
|
-
var import_node_child_process4, import_node_fs10, import_node_os9, import_node_path9, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES, USAGE;
|
|
85547
|
+
var import_node_child_process4, import_node_fs10, import_node_os9, import_node_path9, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES, REPLAY_BRANCH, USAGE;
|
|
85414
85548
|
var init_replay = __esm({
|
|
85415
85549
|
"src/commands/replay.ts"() {
|
|
85416
85550
|
"use strict";
|
|
@@ -85424,10 +85558,15 @@ var init_replay = __esm({
|
|
|
85424
85558
|
ARMS = ["baseline", "haansi"];
|
|
85425
85559
|
FEEDBACK_CHARS = 6e3;
|
|
85426
85560
|
FAILURE_LINES = 8;
|
|
85561
|
+
REPLAY_BRANCH = "replay";
|
|
85427
85562
|
USAGE = `Usage: haansi replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
|
|
85428
85563
|
[--setup-cmd "<cmd>"] [--attempts 3] [--arms baseline,haansi] [--limit n]
|
|
85429
85564
|
[--cutoff <date>] [--timeout-min 30]
|
|
85430
85565
|
[--agent-name <name>] [--model <name>] [--run-ref <url>] [--dry-run]
|
|
85566
|
+
[--repo-commands]
|
|
85567
|
+
|
|
85568
|
+
--repo-commands uses the setup and test commands an org admin set for the
|
|
85569
|
+
repository in Haansi (Settings \u2192 Data sources) for any not given here.
|
|
85431
85570
|
|
|
85432
85571
|
Run from a clone of the repository. Example (GitHub Actions):
|
|
85433
85572
|
haansi replay run --family "$FAMILY_ID" --setup-cmd 'npm ci' \\
|