gnhf 0.1.33 → 0.1.35
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/README.md +3 -1
- package/dist/cli.mjs +276 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ You wake up to a branch full of clean work and a log of everything that happened
|
|
|
47
47
|
- **Dead simple** — one command starts an autonomous loop that runs until you request stop or a configured runtime cap is reached
|
|
48
48
|
- **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries; retryable hard agent errors back off exponentially while agent-reported failures continue immediately
|
|
49
49
|
- **Live terminal title** — interactive runs keep your terminal title updated with live status, token totals, and commit count, then clear or restore it on exit depending on terminal support; token totals prefixed with `~` are estimates
|
|
50
|
+
- **Exit summary**: every run ends with a permanent summary covering elapsed time, branch, iterations, tokens, branch diff stats, local notes/log paths, and review commands
|
|
50
51
|
- **Agent-agnostic**: works with Claude Code, Codex, Rovo Dev, OpenCode, GitHub Copilot CLI, Pi, or ACP targets out of the box
|
|
51
52
|
|
|
52
53
|
## Quick Start
|
|
@@ -137,11 +138,12 @@ npm link
|
|
|
137
138
|
└──────────────────────────────────────┘
|
|
138
139
|
```
|
|
139
140
|
|
|
140
|
-
- **Incremental commits** - each successful iteration is a separate unsigned git commit, so you can cherry-pick or revert individual changes without GPG or SSH signing prompts blocking the run
|
|
141
|
+
- **Incremental commits** - each successful iteration is a separate unsigned git commit, so you can cherry-pick or revert individual changes without GPG or SSH signing prompts blocking the run; if the first commit attempt fails, gnhf re-stages changes and retries with `--no-verify` so hook-mutated work is not stranded
|
|
141
142
|
- **Failure handling** - all failed iterations are rolled back with `git reset --hard`; agent-reported failures proceed to the next iteration immediately, retryable hard agent errors use exponential backoff, and permanent agent errors such as Claude low credit balance abort immediately and print the run log path. Complete no-op iterations are reported as failures and count toward the consecutive-failure abort limit.
|
|
142
143
|
- **Runtime caps** - `--max-iterations` stops before the next iteration begins, `--max-tokens` can abort mid-iteration once reported usage reaches the cap, and `--stop-when` ends the loop after an iteration whose agent output reports the natural-language condition is met; resumed runs reuse the saved stop condition unless you pass a new value, or `--stop-when ""` to clear it; uncommitted work is rolled back in either case, and in the interactive TUI the final state remains visible until you press Ctrl+C to exit
|
|
143
144
|
- **Iteration finalization** - agents are expected to finish validation, stop any background processes they started, and only then emit the final JSON result for the iteration
|
|
144
145
|
- **Graceful interrupts** - in the interactive TUI, the first Ctrl+C requests a graceful stop and lets the current iteration finish (or ends backoff early), the second Ctrl+C force-stops immediately, and `SIGTERM` also force-stops immediately
|
|
146
|
+
- **Exit summary** - after shutdown cleanup, gnhf prints a permanent stdout summary with the final branch, elapsed time, iteration and token totals, branch diff stats, notes/debug-log paths, and review commands
|
|
145
147
|
- **Shared memory** — the agent reads `notes.md` (built up from prior iterations) to communicate across iterations
|
|
146
148
|
- **Local run metadata** — gnhf stores prompt, notes, stop conditions, and commit-message convention metadata under `.gnhf/runs/` and ignores it locally, so your branch only contains intentional work
|
|
147
149
|
- **Resume support** — run `gnhf` while on an existing `gnhf/` branch to pick up where a previous run left off; if you provide a different prompt, gnhf asks whether to update the saved prompt and continue with the existing history, start a new branch, or quit. New runs whose generated branch already exists use a numeric suffix such as `gnhf/<slug>-1`.
|
package/dist/cli.mjs
CHANGED
|
@@ -505,18 +505,84 @@ function getBranchCommitCount(baseCommit, cwd) {
|
|
|
505
505
|
`${baseCommit}..HEAD`
|
|
506
506
|
], cwd), 10);
|
|
507
507
|
}
|
|
508
|
+
function emptyBranchDiffStats$1() {
|
|
509
|
+
return {
|
|
510
|
+
commits: 0,
|
|
511
|
+
filesChanged: 0,
|
|
512
|
+
filesAdded: 0,
|
|
513
|
+
filesUpdated: 0,
|
|
514
|
+
filesDeleted: 0,
|
|
515
|
+
filesRenamed: 0,
|
|
516
|
+
binaryFiles: 0,
|
|
517
|
+
linesAdded: 0,
|
|
518
|
+
linesDeleted: 0
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function getBranchDiffStats(baseCommit, cwd) {
|
|
522
|
+
if (!baseCommit) return emptyBranchDiffStats$1();
|
|
523
|
+
const range = `${baseCommit}..HEAD`;
|
|
524
|
+
const stats = emptyBranchDiffStats$1();
|
|
525
|
+
stats.commits = Number.parseInt(git([
|
|
526
|
+
"rev-list",
|
|
527
|
+
"--count",
|
|
528
|
+
"--first-parent",
|
|
529
|
+
range
|
|
530
|
+
], cwd), 10);
|
|
531
|
+
const nameStatus = git([
|
|
532
|
+
"diff",
|
|
533
|
+
"--name-status",
|
|
534
|
+
range
|
|
535
|
+
], cwd);
|
|
536
|
+
for (const line of nameStatus.split("\n")) {
|
|
537
|
+
if (!line) continue;
|
|
538
|
+
const [status] = line.split(" ");
|
|
539
|
+
stats.filesChanged++;
|
|
540
|
+
if (status === "A") stats.filesAdded++;
|
|
541
|
+
else if (status === "D") stats.filesDeleted++;
|
|
542
|
+
else if (status?.startsWith("R")) {
|
|
543
|
+
stats.filesUpdated++;
|
|
544
|
+
stats.filesRenamed++;
|
|
545
|
+
} else stats.filesUpdated++;
|
|
546
|
+
}
|
|
547
|
+
const numstat = git([
|
|
548
|
+
"diff",
|
|
549
|
+
"--numstat",
|
|
550
|
+
range
|
|
551
|
+
], cwd);
|
|
552
|
+
for (const line of numstat.split("\n")) {
|
|
553
|
+
if (!line) continue;
|
|
554
|
+
const [added, deleted] = line.split(" ");
|
|
555
|
+
if (added === "-" || deleted === "-") {
|
|
556
|
+
stats.binaryFiles++;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
stats.linesAdded += Number.parseInt(added ?? "0", 10) || 0;
|
|
560
|
+
stats.linesDeleted += Number.parseInt(deleted ?? "0", 10) || 0;
|
|
561
|
+
}
|
|
562
|
+
return stats;
|
|
563
|
+
}
|
|
508
564
|
function commitAll(message, cwd) {
|
|
565
|
+
const commitArgs = [
|
|
566
|
+
"-c",
|
|
567
|
+
"commit.gpgsign=false",
|
|
568
|
+
"-c",
|
|
569
|
+
"tag.gpgsign=false",
|
|
570
|
+
"commit",
|
|
571
|
+
"-m",
|
|
572
|
+
message
|
|
573
|
+
];
|
|
509
574
|
git(["add", "-A"], cwd);
|
|
575
|
+
let firstError;
|
|
510
576
|
try {
|
|
511
|
-
git(
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
577
|
+
git(commitArgs, cwd);
|
|
578
|
+
return;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
firstError = error;
|
|
581
|
+
}
|
|
582
|
+
git(["add", "-A"], cwd);
|
|
583
|
+
try {
|
|
584
|
+
git([...commitArgs, "--no-verify"], cwd);
|
|
585
|
+
appendDebugLog("git:commit:no-verify-fallback", { firstError: serializeError(firstError) });
|
|
520
586
|
} catch {}
|
|
521
587
|
}
|
|
522
588
|
function resetHard(cwd) {
|
|
@@ -16784,6 +16850,158 @@ var Orchestrator = class extends EventEmitter {
|
|
|
16784
16850
|
}
|
|
16785
16851
|
};
|
|
16786
16852
|
//#endregion
|
|
16853
|
+
//#region src/utils/tokens.ts
|
|
16854
|
+
function formatTokens(count) {
|
|
16855
|
+
if (count >= 0xe8d4a51000) return `${(count / 0xe8d4a51000).toFixed(1)}T`;
|
|
16856
|
+
if (count >= 1e9) return `${(count / 1e9).toFixed(1)}B`;
|
|
16857
|
+
if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
|
|
16858
|
+
if (count >= 1e3) return `${Math.round(count / 1e3)}K`;
|
|
16859
|
+
return String(count);
|
|
16860
|
+
}
|
|
16861
|
+
//#endregion
|
|
16862
|
+
//#region src/core/exit-summary.ts
|
|
16863
|
+
const MIN_CARD_WIDTH = 62;
|
|
16864
|
+
const LABEL_WIDTH = 16;
|
|
16865
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
16866
|
+
const ANSI_TOKEN_RE = /\x1b\[[0-9;]*m/g;
|
|
16867
|
+
const NO_MISTAKES_URL = "https://github.com/kunchenguid/no-mistakes";
|
|
16868
|
+
function stripExitSummaryAnsi(text) {
|
|
16869
|
+
return text.replace(ANSI_RE, "");
|
|
16870
|
+
}
|
|
16871
|
+
function makeStyles(color) {
|
|
16872
|
+
const wrap = (open, text) => color ? `${open}${text}\x1b[0m` : text;
|
|
16873
|
+
return {
|
|
16874
|
+
dim: (text) => wrap("\x1B[2m", text),
|
|
16875
|
+
bold: (text) => wrap("\x1B[1m", text),
|
|
16876
|
+
cyan: (text) => wrap("\x1B[36m", text),
|
|
16877
|
+
yellow: (text) => wrap("\x1B[33m", text),
|
|
16878
|
+
green: (text) => wrap("\x1B[32m", text),
|
|
16879
|
+
red: (text) => wrap("\x1B[31m", text),
|
|
16880
|
+
magenta: (text) => wrap("\x1B[35m", text),
|
|
16881
|
+
blueUnderline: (text) => wrap("\x1B[34;4m", text)
|
|
16882
|
+
};
|
|
16883
|
+
}
|
|
16884
|
+
function visibleLength(text) {
|
|
16885
|
+
return stripExitSummaryAnsi(text).length;
|
|
16886
|
+
}
|
|
16887
|
+
function padVisible(text, width) {
|
|
16888
|
+
return text + " ".repeat(Math.max(0, width - visibleLength(text)));
|
|
16889
|
+
}
|
|
16890
|
+
function truncateVisible(text, width) {
|
|
16891
|
+
if (visibleLength(text) <= width) return text;
|
|
16892
|
+
if (width <= 0) return "";
|
|
16893
|
+
const targetWidth = Math.max(0, width - 1);
|
|
16894
|
+
let output = "";
|
|
16895
|
+
let visible = 0;
|
|
16896
|
+
let index = 0;
|
|
16897
|
+
let hasActiveStyle = false;
|
|
16898
|
+
const finish = () => `${output}…${hasActiveStyle ? "\x1B[0m" : ""}`;
|
|
16899
|
+
for (const match of text.matchAll(ANSI_TOKEN_RE)) {
|
|
16900
|
+
const chunk = text.slice(index, match.index);
|
|
16901
|
+
for (const char of chunk) {
|
|
16902
|
+
if (visible >= targetWidth) return finish();
|
|
16903
|
+
output += char;
|
|
16904
|
+
visible += 1;
|
|
16905
|
+
}
|
|
16906
|
+
output += match[0];
|
|
16907
|
+
hasActiveStyle = match[0] !== "\x1B[0m";
|
|
16908
|
+
index = match.index + match[0].length;
|
|
16909
|
+
}
|
|
16910
|
+
for (const char of text.slice(index)) {
|
|
16911
|
+
if (visible >= targetWidth) return finish();
|
|
16912
|
+
output += char;
|
|
16913
|
+
visible += 1;
|
|
16914
|
+
}
|
|
16915
|
+
return finish();
|
|
16916
|
+
}
|
|
16917
|
+
function formatDuration(ms) {
|
|
16918
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
16919
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
16920
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
16921
|
+
const seconds = totalSeconds % 60;
|
|
16922
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
16923
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
16924
|
+
return `${seconds}s`;
|
|
16925
|
+
}
|
|
16926
|
+
function formatNumber(value) {
|
|
16927
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
16928
|
+
}
|
|
16929
|
+
function formatTokenCount$1(value, suffix, estimated) {
|
|
16930
|
+
return `${estimated ? "~" : ""}${formatTokens(value)} ${suffix}`;
|
|
16931
|
+
}
|
|
16932
|
+
function plural(value, singular, pluralText = `${singular}s`) {
|
|
16933
|
+
return `${formatNumber(value)} ${value === 1 ? singular : pluralText}`;
|
|
16934
|
+
}
|
|
16935
|
+
function metricLine(label, columns) {
|
|
16936
|
+
return ` ${padVisible(`${padVisible(label, LABEL_WIDTH)}${columns[0] ?? ""}`, 30)}${padVisible(columns[1] ?? "", 13)}${columns[2] ?? ""}`;
|
|
16937
|
+
}
|
|
16938
|
+
function commandLine(label, command) {
|
|
16939
|
+
return ` ${padVisible(label, LABEL_WIDTH)}${command}`;
|
|
16940
|
+
}
|
|
16941
|
+
function continuationLine(text) {
|
|
16942
|
+
return ` ${"".padEnd(LABEL_WIDTH)}${text}`;
|
|
16943
|
+
}
|
|
16944
|
+
function resolveCardWidth(contents, terminalColumns) {
|
|
16945
|
+
const contentWidth = Math.max(...contents.map(visibleLength));
|
|
16946
|
+
const desiredWidth = Math.max(MIN_CARD_WIDTH, contentWidth + 4);
|
|
16947
|
+
const columns = terminalColumns && terminalColumns > 0 ? terminalColumns : void 0;
|
|
16948
|
+
return Math.max(4, columns ? Math.min(desiredWidth, columns) : desiredWidth);
|
|
16949
|
+
}
|
|
16950
|
+
function cardBorder(left, right, width, dim) {
|
|
16951
|
+
return dim(`${left}${"─".repeat(Math.max(0, width - 2))}${right}`);
|
|
16952
|
+
}
|
|
16953
|
+
function cardLine(content, width, dim) {
|
|
16954
|
+
const contentWidth = Math.max(0, width - 4);
|
|
16955
|
+
return `${dim("│ ")}${padVisible(truncateVisible(content, contentWidth), contentWidth)}${dim(" │")}`;
|
|
16956
|
+
}
|
|
16957
|
+
function renderExitSummary(options) {
|
|
16958
|
+
const s = makeStyles(options.color);
|
|
16959
|
+
const elapsed = formatDuration(options.elapsedMs);
|
|
16960
|
+
const stopped = options.status === "aborted";
|
|
16961
|
+
const title = stopped ? `${s.red("×")} ${s.bold("gnhf stopped")}` : `${s.cyan("✦")} ${s.bold("gnhf wrapped")}`;
|
|
16962
|
+
const subtitle = stopped ? `${s.cyan(options.agentName)} ran for ${s.yellow(elapsed)} before: ${options.abortReason ?? options.status}` : `${s.cyan(options.agentName)} worked for ${s.yellow(elapsed)} on ${s.magenta(options.branchName)}`;
|
|
16963
|
+
const cardWidth = resolveCardWidth([title, ` ${subtitle}`], options.terminalColumns);
|
|
16964
|
+
const rolledBack = `${options.failCount} rolled back`;
|
|
16965
|
+
const inputTokens = formatTokenCount$1(options.totalInputTokens, "in", options.tokensEstimated);
|
|
16966
|
+
const outputTokens = formatTokenCount$1(options.totalOutputTokens, "out", options.tokensEstimated);
|
|
16967
|
+
const commits = plural(options.commitCount, "commit");
|
|
16968
|
+
const linesAdded = `+${formatNumber(options.diffStats.linesAdded)}`;
|
|
16969
|
+
const linesDeleted = `-${formatNumber(options.diffStats.linesDeleted)}`;
|
|
16970
|
+
return `\n${[
|
|
16971
|
+
cardBorder("╭", "╮", cardWidth, s.dim),
|
|
16972
|
+
cardLine(title, cardWidth, s.dim),
|
|
16973
|
+
cardLine(` ${subtitle}`, cardWidth, s.dim),
|
|
16974
|
+
cardBorder("╰", "╯", cardWidth, s.dim),
|
|
16975
|
+
"",
|
|
16976
|
+
metricLine(s.dim("iterations"), [
|
|
16977
|
+
`${s.bold(String(options.iterations))} total`,
|
|
16978
|
+
s.green(`${options.successCount} good`),
|
|
16979
|
+
stopped ? s.red(rolledBack) : s.yellow(rolledBack)
|
|
16980
|
+
]),
|
|
16981
|
+
metricLine(s.dim("tokens"), [s.bold(inputTokens), s.bold(outputTokens)]),
|
|
16982
|
+
metricLine(s.dim("branch diff"), [
|
|
16983
|
+
s.bold(commits),
|
|
16984
|
+
s.green(linesAdded),
|
|
16985
|
+
s.red(linesDeleted)
|
|
16986
|
+
]),
|
|
16987
|
+
metricLine(s.dim("files"), [
|
|
16988
|
+
`${options.diffStats.filesAdded} added`,
|
|
16989
|
+
`${options.diffStats.filesUpdated} updated`,
|
|
16990
|
+
`${options.diffStats.filesDeleted} deleted`
|
|
16991
|
+
]),
|
|
16992
|
+
"",
|
|
16993
|
+
commandLine(s.dim("notes"), options.notesPath),
|
|
16994
|
+
commandLine(s.dim("debug log"), options.logPath),
|
|
16995
|
+
"",
|
|
16996
|
+
commandLine(s.dim("next steps"), s.cyan(`git log --oneline ${options.baseRef}..HEAD`)),
|
|
16997
|
+
continuationLine(s.cyan(`git diff --stat ${options.baseRef}..HEAD`)),
|
|
16998
|
+
continuationLine(s.cyan("gh pr create")),
|
|
16999
|
+
"",
|
|
17000
|
+
commandLine(s.dim("too much"), `${s.cyan("git push no-mistakes")}:`),
|
|
17001
|
+
commandLine(s.dim("to review?"), s.blueUnderline(NO_MISTAKES_URL))
|
|
17002
|
+
].join("\n")}\n`;
|
|
17003
|
+
}
|
|
17004
|
+
//#endregion
|
|
16787
17005
|
//#region src/mock-orchestrator.ts
|
|
16788
17006
|
function mockIter(n, success, summary, agoMs) {
|
|
16789
17007
|
return {
|
|
@@ -16997,15 +17215,6 @@ function formatElapsed(ms) {
|
|
|
16997
17215
|
return `${String(Math.floor(s / 3600)).padStart(2, "0")}:${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`;
|
|
16998
17216
|
}
|
|
16999
17217
|
//#endregion
|
|
17000
|
-
//#region src/utils/tokens.ts
|
|
17001
|
-
function formatTokens(count) {
|
|
17002
|
-
if (count >= 0xe8d4a51000) return `${(count / 0xe8d4a51000).toFixed(1)}T`;
|
|
17003
|
-
if (count >= 1e9) return `${(count / 1e9).toFixed(1)}B`;
|
|
17004
|
-
if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
|
|
17005
|
-
if (count >= 1e3) return `${Math.round(count / 1e3)}K`;
|
|
17006
|
-
return String(count);
|
|
17007
|
-
}
|
|
17008
|
-
//#endregion
|
|
17009
17218
|
//#region src/utils/terminal-width.ts
|
|
17010
17219
|
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
17011
17220
|
const MARK_REGEX = /\p{Mark}/u;
|
|
@@ -17656,6 +17865,22 @@ function getNativeAgentName(spec) {
|
|
|
17656
17865
|
function getTelemetryAgent(spec) {
|
|
17657
17866
|
return redactAgentSpecForLogs(spec);
|
|
17658
17867
|
}
|
|
17868
|
+
function shouldUseColor() {
|
|
17869
|
+
return process$1.stdout.isTTY === true && process$1.env.NO_COLOR === void 0 && process$1.env.TERM !== "dumb";
|
|
17870
|
+
}
|
|
17871
|
+
function emptyBranchDiffStats(commitCount) {
|
|
17872
|
+
return {
|
|
17873
|
+
commits: commitCount,
|
|
17874
|
+
filesChanged: 0,
|
|
17875
|
+
filesAdded: 0,
|
|
17876
|
+
filesUpdated: 0,
|
|
17877
|
+
filesDeleted: 0,
|
|
17878
|
+
filesRenamed: 0,
|
|
17879
|
+
binaryFiles: 0,
|
|
17880
|
+
linesAdded: 0,
|
|
17881
|
+
linesDeleted: 0
|
|
17882
|
+
};
|
|
17883
|
+
}
|
|
17659
17884
|
function redactDebugArgs(args) {
|
|
17660
17885
|
const redacted = [...args];
|
|
17661
17886
|
for (let i = 0; i < redacted.length; i += 1) {
|
|
@@ -18135,6 +18360,38 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
18135
18360
|
}
|
|
18136
18361
|
{
|
|
18137
18362
|
const finalState = orchestrator.getState();
|
|
18363
|
+
let finalBranchName = "HEAD";
|
|
18364
|
+
try {
|
|
18365
|
+
finalBranchName = getCurrentBranch(effectiveCwd);
|
|
18366
|
+
} catch (error) {
|
|
18367
|
+
appendDebugLog("summary:branch-error", { error: serializeError(error) });
|
|
18368
|
+
}
|
|
18369
|
+
let diffStats = emptyBranchDiffStats(finalState.commitCount);
|
|
18370
|
+
try {
|
|
18371
|
+
diffStats = getBranchDiffStats(runInfo.baseCommit, effectiveCwd);
|
|
18372
|
+
} catch (error) {
|
|
18373
|
+
appendDebugLog("summary:diff-stats-error", { error: serializeError(error) });
|
|
18374
|
+
}
|
|
18375
|
+
const exitSummary = renderExitSummary({
|
|
18376
|
+
agentName: redactAgentSpecForLogs(config.agent),
|
|
18377
|
+
branchName: finalBranchName,
|
|
18378
|
+
elapsedMs: Date.now() - finalState.startTime.getTime(),
|
|
18379
|
+
status: finalState.status,
|
|
18380
|
+
abortReason: finalState.lastAgentError ?? finalState.lastMessage,
|
|
18381
|
+
iterations: finalState.currentIteration,
|
|
18382
|
+
successCount: finalState.successCount,
|
|
18383
|
+
failCount: finalState.failCount,
|
|
18384
|
+
totalInputTokens: finalState.totalInputTokens,
|
|
18385
|
+
totalOutputTokens: finalState.totalOutputTokens,
|
|
18386
|
+
tokensEstimated: finalState.tokensEstimated,
|
|
18387
|
+
commitCount: finalState.commitCount,
|
|
18388
|
+
notesPath: runInfo.notesPath,
|
|
18389
|
+
logPath: runInfo.logPath,
|
|
18390
|
+
baseRef: runInfo.baseCommit.slice(0, 12) || runInfo.baseCommit,
|
|
18391
|
+
diffStats,
|
|
18392
|
+
color: shouldUseColor(),
|
|
18393
|
+
terminalColumns: process$1.stdout.columns
|
|
18394
|
+
});
|
|
18138
18395
|
appendDebugLog("run:complete", {
|
|
18139
18396
|
signal: shutdownSignal,
|
|
18140
18397
|
status: finalState.status,
|
|
@@ -18172,6 +18429,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
18172
18429
|
worktreeCleanup = null;
|
|
18173
18430
|
appendDebugLog("worktree:cleaned-up", { worktreePath });
|
|
18174
18431
|
}
|
|
18432
|
+
process$1.stdout.write(exitSummary);
|
|
18175
18433
|
}
|
|
18176
18434
|
if (shutdownSignal) process$1.exit(getSignalExitCode(shutdownSignal));
|
|
18177
18435
|
});
|