@gethmy/agent 1.24.0 → 1.25.0
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/cli.js +176 -31
- package/dist/index.js +176 -31
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -654,6 +654,14 @@ var init_classification = __esm(() => {
|
|
|
654
654
|
MODEL_TIERS = ["simple", "advanced", "research"];
|
|
655
655
|
});
|
|
656
656
|
|
|
657
|
+
// ../harmony-shared/dist/columnSort.js
|
|
658
|
+
var init_columnSort = () => {};
|
|
659
|
+
|
|
660
|
+
// ../harmony-shared/dist/columnCardSplit.js
|
|
661
|
+
var init_columnCardSplit = __esm(() => {
|
|
662
|
+
init_columnSort();
|
|
663
|
+
});
|
|
664
|
+
|
|
657
665
|
// ../harmony-shared/dist/commentSerializer.js
|
|
658
666
|
function sanitizeHeaderField(value) {
|
|
659
667
|
return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
|
|
@@ -1440,6 +1448,8 @@ var init_dist = __esm(() => {
|
|
|
1440
1448
|
init_branchRef();
|
|
1441
1449
|
init_cardLinks();
|
|
1442
1450
|
init_classification();
|
|
1451
|
+
init_columnCardSplit();
|
|
1452
|
+
init_columnSort();
|
|
1443
1453
|
init_commentSerializer();
|
|
1444
1454
|
init_constants();
|
|
1445
1455
|
init_gateEvaluate();
|
|
@@ -5684,8 +5694,27 @@ var init_artifact_judge = __esm(() => {
|
|
|
5684
5694
|
init_sdk_agent_runner();
|
|
5685
5695
|
});
|
|
5686
5696
|
|
|
5697
|
+
// src/gate-config-error.ts
|
|
5698
|
+
function gateConfigErrorReason(evaluation) {
|
|
5699
|
+
if (!evaluation || evaluation.passed)
|
|
5700
|
+
return null;
|
|
5701
|
+
const structured = evaluation.structured;
|
|
5702
|
+
if (!structured || typeof structured !== "object")
|
|
5703
|
+
return null;
|
|
5704
|
+
if (!Object.hasOwn(structured, GATE_CONFIG_ERROR_KEY))
|
|
5705
|
+
return null;
|
|
5706
|
+
if (structured[GATE_CONFIG_ERROR_KEY] !== true) {
|
|
5707
|
+
return null;
|
|
5708
|
+
}
|
|
5709
|
+
const reason = structured.reason;
|
|
5710
|
+
return typeof reason === "string" && reason.trim().length > 0 ? reason.trim() : "the gate cannot be measured as configured";
|
|
5711
|
+
}
|
|
5712
|
+
var GATE_CONFIG_ERROR_KEY = "configError", GATE_CONFIG_ERROR_MARK;
|
|
5713
|
+
var init_gate_config_error = __esm(() => {
|
|
5714
|
+
GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
|
|
5715
|
+
});
|
|
5716
|
+
|
|
5687
5717
|
// src/command-metric.ts
|
|
5688
|
-
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
5689
5718
|
function parseParseMode(parse) {
|
|
5690
5719
|
if (typeof parse !== "string" || parse.length === 0) {
|
|
5691
5720
|
return { kind: "invalid", reason: "`parse` must be a non-empty string" };
|
|
@@ -5746,15 +5775,91 @@ function parseMetricValue(mode, stdout) {
|
|
|
5746
5775
|
}
|
|
5747
5776
|
return { ok: true, value: resolved };
|
|
5748
5777
|
}
|
|
5749
|
-
function
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5778
|
+
function runMetricCommand(args) {
|
|
5779
|
+
return new Promise((resolve3, reject) => {
|
|
5780
|
+
let child;
|
|
5781
|
+
try {
|
|
5782
|
+
child = spawnInGroup(args.command, args.args, {
|
|
5783
|
+
cwd: args.cwd,
|
|
5784
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5785
|
+
});
|
|
5786
|
+
} catch (err) {
|
|
5787
|
+
reject(err);
|
|
5788
|
+
return;
|
|
5789
|
+
}
|
|
5790
|
+
const pgid = child.pid;
|
|
5791
|
+
const chunks = [];
|
|
5792
|
+
let stdoutBytes = 0;
|
|
5793
|
+
let stderr = "";
|
|
5794
|
+
let settled = false;
|
|
5795
|
+
let killReason = null;
|
|
5796
|
+
let timer;
|
|
5797
|
+
let drainTimer;
|
|
5798
|
+
const settle = (failure) => {
|
|
5799
|
+
if (settled)
|
|
5800
|
+
return;
|
|
5801
|
+
settled = true;
|
|
5802
|
+
if (timer)
|
|
5803
|
+
clearTimeout(timer);
|
|
5804
|
+
if (drainTimer)
|
|
5805
|
+
clearTimeout(drainTimer);
|
|
5806
|
+
reapGroup(pgid);
|
|
5807
|
+
if (failure)
|
|
5808
|
+
reject(failure);
|
|
5809
|
+
else
|
|
5810
|
+
resolve3(Buffer.concat(chunks).toString("utf8"));
|
|
5811
|
+
};
|
|
5812
|
+
const killTree = (reason) => {
|
|
5813
|
+
if (settled || killReason)
|
|
5814
|
+
return;
|
|
5815
|
+
killReason = reason;
|
|
5816
|
+
terminateGroup(child, {
|
|
5817
|
+
sigintTimeoutMs: METRIC_SIGINT_GRACE_MS,
|
|
5818
|
+
sigtermTimeoutMs: METRIC_SIGTERM_GRACE_MS
|
|
5819
|
+
}).catch(() => {}).then(() => {
|
|
5820
|
+
settle(reason === "timeout" ? Object.assign(new Error(`command timed out after ${args.timeoutMs}ms`), { code: "ETIMEDOUT" }) : Object.assign(new Error("stdout maxBuffer exceeded"), {
|
|
5821
|
+
code: "ENOBUFS"
|
|
5822
|
+
}));
|
|
5823
|
+
});
|
|
5824
|
+
};
|
|
5825
|
+
child.stdout?.on("data", (chunk) => {
|
|
5826
|
+
stdoutBytes += chunk.length;
|
|
5827
|
+
if (stdoutBytes > MAX_OUTPUT_BUFFER2) {
|
|
5828
|
+
killTree("overflow");
|
|
5829
|
+
return;
|
|
5830
|
+
}
|
|
5831
|
+
chunks.push(chunk);
|
|
5832
|
+
});
|
|
5833
|
+
child.stderr?.on("data", (chunk) => {
|
|
5834
|
+
if (stderr.length >= MAX_STDERR_CHARS)
|
|
5835
|
+
return;
|
|
5836
|
+
stderr += chunk.toString("utf8");
|
|
5837
|
+
});
|
|
5838
|
+
child.once("error", (err) => settle(err));
|
|
5839
|
+
const settleFromExit = (code, signal) => {
|
|
5840
|
+
if (drainTimer)
|
|
5841
|
+
clearTimeout(drainTimer);
|
|
5842
|
+
if (code === 0) {
|
|
5843
|
+
settle(null);
|
|
5844
|
+
return;
|
|
5845
|
+
}
|
|
5846
|
+
const detail = signal ? `terminated by signal ${signal}` : `exited ${code}`;
|
|
5847
|
+
settle(Object.assign(new Error(`command ${detail}`), {
|
|
5848
|
+
status: typeof code === "number" ? code : null,
|
|
5849
|
+
stderr
|
|
5850
|
+
}));
|
|
5851
|
+
};
|
|
5852
|
+
child.once("exit", (code, signal) => {
|
|
5853
|
+
if (killReason)
|
|
5854
|
+
return;
|
|
5855
|
+
if (timer)
|
|
5856
|
+
clearTimeout(timer);
|
|
5857
|
+
reapGroup(pgid);
|
|
5858
|
+
drainTimer = setTimeout(() => settleFromExit(code, signal), STDIO_DRAIN_GRACE_MS);
|
|
5859
|
+
child.once("close", () => settleFromExit(code, signal));
|
|
5860
|
+
});
|
|
5861
|
+
timer = setTimeout(() => killTree("timeout"), args.timeoutMs);
|
|
5756
5862
|
});
|
|
5757
|
-
return typeof out === "string" ? out : String(out);
|
|
5758
5863
|
}
|
|
5759
5864
|
|
|
5760
5865
|
class CommandMetricCollector {
|
|
@@ -5766,28 +5871,28 @@ class CommandMetricCollector {
|
|
|
5766
5871
|
async collect(context) {
|
|
5767
5872
|
const name = context.gate.metric;
|
|
5768
5873
|
if (typeof name !== "string" || name.length === 0) {
|
|
5769
|
-
return
|
|
5874
|
+
return configError(null, 'Gate kind "custom" needs a `metric` name naming an allowlisted command (e.g. { "kind": "custom", "metric": "lighthouse_performance" }).');
|
|
5770
5875
|
}
|
|
5771
5876
|
const def = Object.hasOwn(this.deps.metrics, name) ? this.deps.metrics[name] : undefined;
|
|
5772
5877
|
if (!def) {
|
|
5773
|
-
return
|
|
5878
|
+
return configError(name, `Metric "${name}" is not declared in this daemon's allowlist — add it under \`agent.playbooks.metrics\` to permit it.`);
|
|
5774
5879
|
}
|
|
5775
5880
|
if (typeof def.command !== "string" || def.command.length === 0) {
|
|
5776
|
-
return
|
|
5881
|
+
return configError(name, `Metric "${name}" declares no \`command\`.`);
|
|
5777
5882
|
}
|
|
5778
5883
|
const mode = parseParseMode(def.parse);
|
|
5779
5884
|
if (mode.kind === "invalid") {
|
|
5780
|
-
return
|
|
5885
|
+
return configError(name, `Metric "${name}": ${mode.reason}.`);
|
|
5781
5886
|
}
|
|
5782
5887
|
const requested = typeof def.timeoutMs === "number" && def.timeoutMs > 0 ? Math.floor(def.timeoutMs) : DEFAULT_METRIC_TIMEOUT_MS;
|
|
5783
5888
|
const timeoutMs = Math.min(requested, MAX_METRIC_TIMEOUT_MS);
|
|
5784
5889
|
if (timeoutMs < requested) {
|
|
5785
5890
|
log.warn(TAG19, `Metric "${name}" declares timeoutMs=${requested}, clamped to the ${MAX_METRIC_TIMEOUT_MS}ms ceiling`);
|
|
5786
5891
|
}
|
|
5787
|
-
const run = this.deps.runCommand ??
|
|
5892
|
+
const run = this.deps.runCommand ?? runMetricCommand;
|
|
5788
5893
|
let stdout;
|
|
5789
5894
|
try {
|
|
5790
|
-
stdout = run({
|
|
5895
|
+
stdout = await run({
|
|
5791
5896
|
command: def.command,
|
|
5792
5897
|
args: def.args ?? [],
|
|
5793
5898
|
cwd: this.deps.worktreePath,
|
|
@@ -5824,6 +5929,13 @@ function blocked(metric, reason, raw) {
|
|
|
5824
5929
|
}
|
|
5825
5930
|
};
|
|
5826
5931
|
}
|
|
5932
|
+
function configError(metric, reason) {
|
|
5933
|
+
const evidence = blocked(metric, reason);
|
|
5934
|
+
return {
|
|
5935
|
+
...evidence,
|
|
5936
|
+
structured: { ...evidence.structured, ...GATE_CONFIG_ERROR_MARK }
|
|
5937
|
+
};
|
|
5938
|
+
}
|
|
5827
5939
|
function describeRunFailure(err, timeoutMs) {
|
|
5828
5940
|
const e = err;
|
|
5829
5941
|
if (e?.code === "ENOBUFS") {
|
|
@@ -5843,12 +5955,15 @@ function describeRunFailure(err, timeoutMs) {
|
|
|
5843
5955
|
function truncate(value, max) {
|
|
5844
5956
|
return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
|
|
5845
5957
|
}
|
|
5846
|
-
var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_METRIC_TIMEOUT_MS = 900000;
|
|
5958
|
+
var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_STDERR_CHARS, MAX_METRIC_TIMEOUT_MS = 900000, METRIC_SIGINT_GRACE_MS = 2000, METRIC_SIGTERM_GRACE_MS = 3000, STDIO_DRAIN_GRACE_MS = 500;
|
|
5847
5959
|
var init_command_metric = __esm(() => {
|
|
5848
5960
|
init_dist();
|
|
5961
|
+
init_gate_config_error();
|
|
5849
5962
|
init_log();
|
|
5963
|
+
init_process_group();
|
|
5850
5964
|
init_types2();
|
|
5851
5965
|
MAX_OUTPUT_BUFFER2 = 10 * 1024 * 1024;
|
|
5966
|
+
MAX_STDERR_CHARS = 64 * 1024;
|
|
5852
5967
|
});
|
|
5853
5968
|
|
|
5854
5969
|
// src/gate-collectors.ts
|
|
@@ -7548,7 +7663,7 @@ var init_transitions = __esm(() => {
|
|
|
7548
7663
|
});
|
|
7549
7664
|
|
|
7550
7665
|
// src/review-worker.ts
|
|
7551
|
-
import { execFileSync as
|
|
7666
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
7552
7667
|
|
|
7553
7668
|
class ReviewWorker {
|
|
7554
7669
|
config;
|
|
@@ -7666,7 +7781,7 @@ class ReviewWorker {
|
|
|
7666
7781
|
costCents: 0,
|
|
7667
7782
|
numTurns: 0
|
|
7668
7783
|
});
|
|
7669
|
-
const repoRoot =
|
|
7784
|
+
const repoRoot = execFileSync10("git", ["rev-parse", "--show-toplevel"], {
|
|
7670
7785
|
encoding: "utf-8",
|
|
7671
7786
|
timeout: 5000
|
|
7672
7787
|
}).trim();
|
|
@@ -7735,7 +7850,7 @@ class ReviewWorker {
|
|
|
7735
7850
|
return;
|
|
7736
7851
|
let diff = "";
|
|
7737
7852
|
try {
|
|
7738
|
-
diff =
|
|
7853
|
+
diff = execFileSync10("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
|
|
7739
7854
|
} catch {
|
|
7740
7855
|
diff = "(unable to retrieve diff)";
|
|
7741
7856
|
}
|
|
@@ -8574,8 +8689,11 @@ var init_prompt = __esm(() => {
|
|
|
8574
8689
|
});
|
|
8575
8690
|
|
|
8576
8691
|
// src/stage-advance.ts
|
|
8692
|
+
function gateKindOf(stage) {
|
|
8693
|
+
return stage.gate && typeof stage.gate === "object" ? String(stage.gate.kind ?? "gate") : "gate";
|
|
8694
|
+
}
|
|
8577
8695
|
function gateSummary(stage, evaluation) {
|
|
8578
|
-
const kind =
|
|
8696
|
+
const kind = gateKindOf(stage);
|
|
8579
8697
|
if (evaluation.passed)
|
|
8580
8698
|
return `${kind} passed`;
|
|
8581
8699
|
const firstError = evaluation.findings.find((f) => f.level === "error");
|
|
@@ -8598,6 +8716,10 @@ async function persistStagePointer(client, card, body) {
|
|
|
8598
8716
|
await client.request("POST", `/cards/${encodeURIComponent(card.id)}/advance-stage`, body);
|
|
8599
8717
|
}
|
|
8600
8718
|
async function advanceStageRun(card, stage, stageIndex, def, evaluation, deps) {
|
|
8719
|
+
const configErrorDetail = gateConfigErrorReason(evaluation);
|
|
8720
|
+
if (configErrorDetail) {
|
|
8721
|
+
return holdGateMisconfigured(card, stage, configErrorDetail, deps);
|
|
8722
|
+
}
|
|
8601
8723
|
const loop = getStageLoop(stage);
|
|
8602
8724
|
if (!isConvergeLoop(loop)) {
|
|
8603
8725
|
if (!evaluation)
|
|
@@ -8606,6 +8728,28 @@ async function advanceStageRun(card, stage, stageIndex, def, evaluation, deps) {
|
|
|
8606
8728
|
}
|
|
8607
8729
|
return advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loop, deps);
|
|
8608
8730
|
}
|
|
8731
|
+
async function holdGateMisconfigured(card, stage, detail, deps) {
|
|
8732
|
+
const reason = `Stage "${stage.name}" cannot be evaluated as configured — ${detail} Holding for a human: this is a configuration defect, so re-running the stage would reach the same result. No attempt was charged.`;
|
|
8733
|
+
deps.sink?.recordStageGateEvaluated({
|
|
8734
|
+
stageId: stage.id,
|
|
8735
|
+
gateId: gateKindOf(stage),
|
|
8736
|
+
verdict: "blocked",
|
|
8737
|
+
evidence: detail,
|
|
8738
|
+
summary: `${gateKindOf(stage)} misconfigured`
|
|
8739
|
+
});
|
|
8740
|
+
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
8741
|
+
try {
|
|
8742
|
+
await deps.client.updateAgentProgress(card.id, {
|
|
8743
|
+
agentIdentifier: "claude-code-stage",
|
|
8744
|
+
agentName: "Harmony Agent",
|
|
8745
|
+
status: "waiting",
|
|
8746
|
+
currentTask: reason
|
|
8747
|
+
});
|
|
8748
|
+
} catch {}
|
|
8749
|
+
await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore);
|
|
8750
|
+
log.info(TAG31, `#${card.short_id} GateMisconfigured: ${reason}`);
|
|
8751
|
+
return { kind: "held_misconfigured", reason };
|
|
8752
|
+
}
|
|
8609
8753
|
function firstErrorMessage(evaluation) {
|
|
8610
8754
|
const e = evaluation?.findings.find((f) => f.level === "error");
|
|
8611
8755
|
return e ? e.message : null;
|
|
@@ -8716,7 +8860,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
8716
8860
|
const summary = gateSummary(stage, evaluation);
|
|
8717
8861
|
deps.sink?.recordStageGateEvaluated({
|
|
8718
8862
|
stageId: stage.id,
|
|
8719
|
-
gateId:
|
|
8863
|
+
gateId: gateKindOf(stage),
|
|
8720
8864
|
verdict: evaluation.passed ? "passed" : "failed",
|
|
8721
8865
|
evidence: evaluation.findings.map((f) => `[${f.level}] ${f.message}`).join(`
|
|
8722
8866
|
`),
|
|
@@ -8829,6 +8973,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
8829
8973
|
var TAG31 = "stage-advance", AGENT_LABEL = "agent";
|
|
8830
8974
|
var init_stage_advance = __esm(() => {
|
|
8831
8975
|
init_dist();
|
|
8976
|
+
init_gate_config_error();
|
|
8832
8977
|
init_log();
|
|
8833
8978
|
init_transitions();
|
|
8834
8979
|
});
|
|
@@ -11381,7 +11526,7 @@ __export(exports_worktree_gc, {
|
|
|
11381
11526
|
isTransientGitNetworkError: () => isTransientGitNetworkError,
|
|
11382
11527
|
WorktreeGc: () => WorktreeGc
|
|
11383
11528
|
});
|
|
11384
|
-
import { execFileSync as
|
|
11529
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
11385
11530
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
11386
11531
|
import { resolve as resolve3 } from "node:path";
|
|
11387
11532
|
function isTransientGitNetworkError(message) {
|
|
@@ -11449,7 +11594,7 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
11449
11594
|
}
|
|
11450
11595
|
}
|
|
11451
11596
|
try {
|
|
11452
|
-
|
|
11597
|
+
execFileSync11("git", ["worktree", "prune", "--expire=now"], {
|
|
11453
11598
|
cwd: repoRoot,
|
|
11454
11599
|
stdio: "pipe"
|
|
11455
11600
|
});
|
|
@@ -11480,7 +11625,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11480
11625
|
return result;
|
|
11481
11626
|
}
|
|
11482
11627
|
try {
|
|
11483
|
-
|
|
11628
|
+
execFileSync11("git", ["fetch", "--prune", "origin"], {
|
|
11484
11629
|
cwd: repoRoot,
|
|
11485
11630
|
stdio: "pipe",
|
|
11486
11631
|
...GIT_NETWORK_EXEC
|
|
@@ -11496,7 +11641,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11496
11641
|
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
11497
11642
|
let listing = "";
|
|
11498
11643
|
try {
|
|
11499
|
-
listing =
|
|
11644
|
+
listing = execFileSync11("git", [
|
|
11500
11645
|
"for-each-ref",
|
|
11501
11646
|
"--format=%(refname:strip=3) %(committerdate:unix)",
|
|
11502
11647
|
refPattern
|
|
@@ -11531,7 +11676,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11531
11676
|
break;
|
|
11532
11677
|
}
|
|
11533
11678
|
try {
|
|
11534
|
-
|
|
11679
|
+
execFileSync11("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
11535
11680
|
cwd: repoRoot,
|
|
11536
11681
|
stdio: "pipe",
|
|
11537
11682
|
...GIT_NETWORK_EXEC
|
|
@@ -11594,7 +11739,7 @@ class WorktreeGc {
|
|
|
11594
11739
|
}
|
|
11595
11740
|
function getRepoRoot2() {
|
|
11596
11741
|
try {
|
|
11597
|
-
return
|
|
11742
|
+
return execFileSync11("git", ["rev-parse", "--show-toplevel"], {
|
|
11598
11743
|
encoding: "utf-8"
|
|
11599
11744
|
}).trim();
|
|
11600
11745
|
} catch {
|
|
@@ -11635,12 +11780,12 @@ __export(exports_src, {
|
|
|
11635
11780
|
validatePrerequisites: () => validatePrerequisites,
|
|
11636
11781
|
main: () => main
|
|
11637
11782
|
});
|
|
11638
|
-
import { execFileSync as
|
|
11783
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
11639
11784
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11640
11785
|
import { createRequire as createRequire2 } from "node:module";
|
|
11641
11786
|
async function validatePrerequisites(config, banner) {
|
|
11642
11787
|
try {
|
|
11643
|
-
const ver =
|
|
11788
|
+
const ver = execFileSync12("claude", ["--version"], {
|
|
11644
11789
|
encoding: "utf-8"
|
|
11645
11790
|
}).trim();
|
|
11646
11791
|
banner.check(`Claude CLI ${ver}`);
|
|
@@ -11655,14 +11800,14 @@ async function validatePrerequisites(config, banner) {
|
|
|
11655
11800
|
validateGitProviderCli(provider);
|
|
11656
11801
|
}
|
|
11657
11802
|
try {
|
|
11658
|
-
const status =
|
|
11803
|
+
const status = execFileSync12("git", ["status", "--porcelain"], {
|
|
11659
11804
|
encoding: "utf-8"
|
|
11660
11805
|
}).trim();
|
|
11661
11806
|
if (status) {
|
|
11662
11807
|
banner.warn(`Working directory has uncommitted changes:
|
|
11663
11808
|
${status}`);
|
|
11664
11809
|
}
|
|
11665
|
-
|
|
11810
|
+
execFileSync12("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
|
|
11666
11811
|
encoding: "utf-8",
|
|
11667
11812
|
stdio: "pipe"
|
|
11668
11813
|
});
|
package/dist/index.js
CHANGED
|
@@ -653,6 +653,14 @@ var init_classification = __esm(() => {
|
|
|
653
653
|
MODEL_TIERS = ["simple", "advanced", "research"];
|
|
654
654
|
});
|
|
655
655
|
|
|
656
|
+
// ../harmony-shared/dist/columnSort.js
|
|
657
|
+
var init_columnSort = () => {};
|
|
658
|
+
|
|
659
|
+
// ../harmony-shared/dist/columnCardSplit.js
|
|
660
|
+
var init_columnCardSplit = __esm(() => {
|
|
661
|
+
init_columnSort();
|
|
662
|
+
});
|
|
663
|
+
|
|
656
664
|
// ../harmony-shared/dist/commentSerializer.js
|
|
657
665
|
function sanitizeHeaderField(value) {
|
|
658
666
|
return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
|
|
@@ -1439,6 +1447,8 @@ var init_dist = __esm(() => {
|
|
|
1439
1447
|
init_branchRef();
|
|
1440
1448
|
init_cardLinks();
|
|
1441
1449
|
init_classification();
|
|
1450
|
+
init_columnCardSplit();
|
|
1451
|
+
init_columnSort();
|
|
1442
1452
|
init_commentSerializer();
|
|
1443
1453
|
init_constants();
|
|
1444
1454
|
init_gateEvaluate();
|
|
@@ -5683,8 +5693,27 @@ var init_artifact_judge = __esm(() => {
|
|
|
5683
5693
|
init_sdk_agent_runner();
|
|
5684
5694
|
});
|
|
5685
5695
|
|
|
5696
|
+
// src/gate-config-error.ts
|
|
5697
|
+
function gateConfigErrorReason(evaluation) {
|
|
5698
|
+
if (!evaluation || evaluation.passed)
|
|
5699
|
+
return null;
|
|
5700
|
+
const structured = evaluation.structured;
|
|
5701
|
+
if (!structured || typeof structured !== "object")
|
|
5702
|
+
return null;
|
|
5703
|
+
if (!Object.hasOwn(structured, GATE_CONFIG_ERROR_KEY))
|
|
5704
|
+
return null;
|
|
5705
|
+
if (structured[GATE_CONFIG_ERROR_KEY] !== true) {
|
|
5706
|
+
return null;
|
|
5707
|
+
}
|
|
5708
|
+
const reason = structured.reason;
|
|
5709
|
+
return typeof reason === "string" && reason.trim().length > 0 ? reason.trim() : "the gate cannot be measured as configured";
|
|
5710
|
+
}
|
|
5711
|
+
var GATE_CONFIG_ERROR_KEY = "configError", GATE_CONFIG_ERROR_MARK;
|
|
5712
|
+
var init_gate_config_error = __esm(() => {
|
|
5713
|
+
GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
|
|
5714
|
+
});
|
|
5715
|
+
|
|
5686
5716
|
// src/command-metric.ts
|
|
5687
|
-
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
5688
5717
|
function parseParseMode(parse) {
|
|
5689
5718
|
if (typeof parse !== "string" || parse.length === 0) {
|
|
5690
5719
|
return { kind: "invalid", reason: "`parse` must be a non-empty string" };
|
|
@@ -5745,15 +5774,91 @@ function parseMetricValue(mode, stdout) {
|
|
|
5745
5774
|
}
|
|
5746
5775
|
return { ok: true, value: resolved };
|
|
5747
5776
|
}
|
|
5748
|
-
function
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5777
|
+
function runMetricCommand(args) {
|
|
5778
|
+
return new Promise((resolve3, reject) => {
|
|
5779
|
+
let child;
|
|
5780
|
+
try {
|
|
5781
|
+
child = spawnInGroup(args.command, args.args, {
|
|
5782
|
+
cwd: args.cwd,
|
|
5783
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5784
|
+
});
|
|
5785
|
+
} catch (err) {
|
|
5786
|
+
reject(err);
|
|
5787
|
+
return;
|
|
5788
|
+
}
|
|
5789
|
+
const pgid = child.pid;
|
|
5790
|
+
const chunks = [];
|
|
5791
|
+
let stdoutBytes = 0;
|
|
5792
|
+
let stderr = "";
|
|
5793
|
+
let settled = false;
|
|
5794
|
+
let killReason = null;
|
|
5795
|
+
let timer;
|
|
5796
|
+
let drainTimer;
|
|
5797
|
+
const settle = (failure) => {
|
|
5798
|
+
if (settled)
|
|
5799
|
+
return;
|
|
5800
|
+
settled = true;
|
|
5801
|
+
if (timer)
|
|
5802
|
+
clearTimeout(timer);
|
|
5803
|
+
if (drainTimer)
|
|
5804
|
+
clearTimeout(drainTimer);
|
|
5805
|
+
reapGroup(pgid);
|
|
5806
|
+
if (failure)
|
|
5807
|
+
reject(failure);
|
|
5808
|
+
else
|
|
5809
|
+
resolve3(Buffer.concat(chunks).toString("utf8"));
|
|
5810
|
+
};
|
|
5811
|
+
const killTree = (reason) => {
|
|
5812
|
+
if (settled || killReason)
|
|
5813
|
+
return;
|
|
5814
|
+
killReason = reason;
|
|
5815
|
+
terminateGroup(child, {
|
|
5816
|
+
sigintTimeoutMs: METRIC_SIGINT_GRACE_MS,
|
|
5817
|
+
sigtermTimeoutMs: METRIC_SIGTERM_GRACE_MS
|
|
5818
|
+
}).catch(() => {}).then(() => {
|
|
5819
|
+
settle(reason === "timeout" ? Object.assign(new Error(`command timed out after ${args.timeoutMs}ms`), { code: "ETIMEDOUT" }) : Object.assign(new Error("stdout maxBuffer exceeded"), {
|
|
5820
|
+
code: "ENOBUFS"
|
|
5821
|
+
}));
|
|
5822
|
+
});
|
|
5823
|
+
};
|
|
5824
|
+
child.stdout?.on("data", (chunk) => {
|
|
5825
|
+
stdoutBytes += chunk.length;
|
|
5826
|
+
if (stdoutBytes > MAX_OUTPUT_BUFFER2) {
|
|
5827
|
+
killTree("overflow");
|
|
5828
|
+
return;
|
|
5829
|
+
}
|
|
5830
|
+
chunks.push(chunk);
|
|
5831
|
+
});
|
|
5832
|
+
child.stderr?.on("data", (chunk) => {
|
|
5833
|
+
if (stderr.length >= MAX_STDERR_CHARS)
|
|
5834
|
+
return;
|
|
5835
|
+
stderr += chunk.toString("utf8");
|
|
5836
|
+
});
|
|
5837
|
+
child.once("error", (err) => settle(err));
|
|
5838
|
+
const settleFromExit = (code, signal) => {
|
|
5839
|
+
if (drainTimer)
|
|
5840
|
+
clearTimeout(drainTimer);
|
|
5841
|
+
if (code === 0) {
|
|
5842
|
+
settle(null);
|
|
5843
|
+
return;
|
|
5844
|
+
}
|
|
5845
|
+
const detail = signal ? `terminated by signal ${signal}` : `exited ${code}`;
|
|
5846
|
+
settle(Object.assign(new Error(`command ${detail}`), {
|
|
5847
|
+
status: typeof code === "number" ? code : null,
|
|
5848
|
+
stderr
|
|
5849
|
+
}));
|
|
5850
|
+
};
|
|
5851
|
+
child.once("exit", (code, signal) => {
|
|
5852
|
+
if (killReason)
|
|
5853
|
+
return;
|
|
5854
|
+
if (timer)
|
|
5855
|
+
clearTimeout(timer);
|
|
5856
|
+
reapGroup(pgid);
|
|
5857
|
+
drainTimer = setTimeout(() => settleFromExit(code, signal), STDIO_DRAIN_GRACE_MS);
|
|
5858
|
+
child.once("close", () => settleFromExit(code, signal));
|
|
5859
|
+
});
|
|
5860
|
+
timer = setTimeout(() => killTree("timeout"), args.timeoutMs);
|
|
5755
5861
|
});
|
|
5756
|
-
return typeof out === "string" ? out : String(out);
|
|
5757
5862
|
}
|
|
5758
5863
|
|
|
5759
5864
|
class CommandMetricCollector {
|
|
@@ -5765,28 +5870,28 @@ class CommandMetricCollector {
|
|
|
5765
5870
|
async collect(context) {
|
|
5766
5871
|
const name = context.gate.metric;
|
|
5767
5872
|
if (typeof name !== "string" || name.length === 0) {
|
|
5768
|
-
return
|
|
5873
|
+
return configError(null, 'Gate kind "custom" needs a `metric` name naming an allowlisted command (e.g. { "kind": "custom", "metric": "lighthouse_performance" }).');
|
|
5769
5874
|
}
|
|
5770
5875
|
const def = Object.hasOwn(this.deps.metrics, name) ? this.deps.metrics[name] : undefined;
|
|
5771
5876
|
if (!def) {
|
|
5772
|
-
return
|
|
5877
|
+
return configError(name, `Metric "${name}" is not declared in this daemon's allowlist — add it under \`agent.playbooks.metrics\` to permit it.`);
|
|
5773
5878
|
}
|
|
5774
5879
|
if (typeof def.command !== "string" || def.command.length === 0) {
|
|
5775
|
-
return
|
|
5880
|
+
return configError(name, `Metric "${name}" declares no \`command\`.`);
|
|
5776
5881
|
}
|
|
5777
5882
|
const mode = parseParseMode(def.parse);
|
|
5778
5883
|
if (mode.kind === "invalid") {
|
|
5779
|
-
return
|
|
5884
|
+
return configError(name, `Metric "${name}": ${mode.reason}.`);
|
|
5780
5885
|
}
|
|
5781
5886
|
const requested = typeof def.timeoutMs === "number" && def.timeoutMs > 0 ? Math.floor(def.timeoutMs) : DEFAULT_METRIC_TIMEOUT_MS;
|
|
5782
5887
|
const timeoutMs = Math.min(requested, MAX_METRIC_TIMEOUT_MS);
|
|
5783
5888
|
if (timeoutMs < requested) {
|
|
5784
5889
|
log.warn(TAG19, `Metric "${name}" declares timeoutMs=${requested}, clamped to the ${MAX_METRIC_TIMEOUT_MS}ms ceiling`);
|
|
5785
5890
|
}
|
|
5786
|
-
const run = this.deps.runCommand ??
|
|
5891
|
+
const run = this.deps.runCommand ?? runMetricCommand;
|
|
5787
5892
|
let stdout;
|
|
5788
5893
|
try {
|
|
5789
|
-
stdout = run({
|
|
5894
|
+
stdout = await run({
|
|
5790
5895
|
command: def.command,
|
|
5791
5896
|
args: def.args ?? [],
|
|
5792
5897
|
cwd: this.deps.worktreePath,
|
|
@@ -5823,6 +5928,13 @@ function blocked(metric, reason, raw) {
|
|
|
5823
5928
|
}
|
|
5824
5929
|
};
|
|
5825
5930
|
}
|
|
5931
|
+
function configError(metric, reason) {
|
|
5932
|
+
const evidence = blocked(metric, reason);
|
|
5933
|
+
return {
|
|
5934
|
+
...evidence,
|
|
5935
|
+
structured: { ...evidence.structured, ...GATE_CONFIG_ERROR_MARK }
|
|
5936
|
+
};
|
|
5937
|
+
}
|
|
5826
5938
|
function describeRunFailure(err, timeoutMs) {
|
|
5827
5939
|
const e = err;
|
|
5828
5940
|
if (e?.code === "ENOBUFS") {
|
|
@@ -5842,12 +5954,15 @@ function describeRunFailure(err, timeoutMs) {
|
|
|
5842
5954
|
function truncate(value, max) {
|
|
5843
5955
|
return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
|
|
5844
5956
|
}
|
|
5845
|
-
var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_METRIC_TIMEOUT_MS = 900000;
|
|
5957
|
+
var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_STDERR_CHARS, MAX_METRIC_TIMEOUT_MS = 900000, METRIC_SIGINT_GRACE_MS = 2000, METRIC_SIGTERM_GRACE_MS = 3000, STDIO_DRAIN_GRACE_MS = 500;
|
|
5846
5958
|
var init_command_metric = __esm(() => {
|
|
5847
5959
|
init_dist();
|
|
5960
|
+
init_gate_config_error();
|
|
5848
5961
|
init_log();
|
|
5962
|
+
init_process_group();
|
|
5849
5963
|
init_types2();
|
|
5850
5964
|
MAX_OUTPUT_BUFFER2 = 10 * 1024 * 1024;
|
|
5965
|
+
MAX_STDERR_CHARS = 64 * 1024;
|
|
5851
5966
|
});
|
|
5852
5967
|
|
|
5853
5968
|
// src/gate-collectors.ts
|
|
@@ -7547,7 +7662,7 @@ var init_transitions = __esm(() => {
|
|
|
7547
7662
|
});
|
|
7548
7663
|
|
|
7549
7664
|
// src/review-worker.ts
|
|
7550
|
-
import { execFileSync as
|
|
7665
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
7551
7666
|
|
|
7552
7667
|
class ReviewWorker {
|
|
7553
7668
|
config;
|
|
@@ -7665,7 +7780,7 @@ class ReviewWorker {
|
|
|
7665
7780
|
costCents: 0,
|
|
7666
7781
|
numTurns: 0
|
|
7667
7782
|
});
|
|
7668
|
-
const repoRoot =
|
|
7783
|
+
const repoRoot = execFileSync10("git", ["rev-parse", "--show-toplevel"], {
|
|
7669
7784
|
encoding: "utf-8",
|
|
7670
7785
|
timeout: 5000
|
|
7671
7786
|
}).trim();
|
|
@@ -7734,7 +7849,7 @@ class ReviewWorker {
|
|
|
7734
7849
|
return;
|
|
7735
7850
|
let diff = "";
|
|
7736
7851
|
try {
|
|
7737
|
-
diff =
|
|
7852
|
+
diff = execFileSync10("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
|
|
7738
7853
|
} catch {
|
|
7739
7854
|
diff = "(unable to retrieve diff)";
|
|
7740
7855
|
}
|
|
@@ -8573,8 +8688,11 @@ var init_prompt = __esm(() => {
|
|
|
8573
8688
|
});
|
|
8574
8689
|
|
|
8575
8690
|
// src/stage-advance.ts
|
|
8691
|
+
function gateKindOf(stage) {
|
|
8692
|
+
return stage.gate && typeof stage.gate === "object" ? String(stage.gate.kind ?? "gate") : "gate";
|
|
8693
|
+
}
|
|
8576
8694
|
function gateSummary(stage, evaluation) {
|
|
8577
|
-
const kind =
|
|
8695
|
+
const kind = gateKindOf(stage);
|
|
8578
8696
|
if (evaluation.passed)
|
|
8579
8697
|
return `${kind} passed`;
|
|
8580
8698
|
const firstError = evaluation.findings.find((f) => f.level === "error");
|
|
@@ -8597,6 +8715,10 @@ async function persistStagePointer(client, card, body) {
|
|
|
8597
8715
|
await client.request("POST", `/cards/${encodeURIComponent(card.id)}/advance-stage`, body);
|
|
8598
8716
|
}
|
|
8599
8717
|
async function advanceStageRun(card, stage, stageIndex, def, evaluation, deps) {
|
|
8718
|
+
const configErrorDetail = gateConfigErrorReason(evaluation);
|
|
8719
|
+
if (configErrorDetail) {
|
|
8720
|
+
return holdGateMisconfigured(card, stage, configErrorDetail, deps);
|
|
8721
|
+
}
|
|
8600
8722
|
const loop = getStageLoop(stage);
|
|
8601
8723
|
if (!isConvergeLoop(loop)) {
|
|
8602
8724
|
if (!evaluation)
|
|
@@ -8605,6 +8727,28 @@ async function advanceStageRun(card, stage, stageIndex, def, evaluation, deps) {
|
|
|
8605
8727
|
}
|
|
8606
8728
|
return advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loop, deps);
|
|
8607
8729
|
}
|
|
8730
|
+
async function holdGateMisconfigured(card, stage, detail, deps) {
|
|
8731
|
+
const reason = `Stage "${stage.name}" cannot be evaluated as configured — ${detail} Holding for a human: this is a configuration defect, so re-running the stage would reach the same result. No attempt was charged.`;
|
|
8732
|
+
deps.sink?.recordStageGateEvaluated({
|
|
8733
|
+
stageId: stage.id,
|
|
8734
|
+
gateId: gateKindOf(stage),
|
|
8735
|
+
verdict: "blocked",
|
|
8736
|
+
evidence: detail,
|
|
8737
|
+
summary: `${gateKindOf(stage)} misconfigured`
|
|
8738
|
+
});
|
|
8739
|
+
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
8740
|
+
try {
|
|
8741
|
+
await deps.client.updateAgentProgress(card.id, {
|
|
8742
|
+
agentIdentifier: "claude-code-stage",
|
|
8743
|
+
agentName: "Harmony Agent",
|
|
8744
|
+
status: "waiting",
|
|
8745
|
+
currentTask: reason
|
|
8746
|
+
});
|
|
8747
|
+
} catch {}
|
|
8748
|
+
await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore);
|
|
8749
|
+
log.info(TAG31, `#${card.short_id} GateMisconfigured: ${reason}`);
|
|
8750
|
+
return { kind: "held_misconfigured", reason };
|
|
8751
|
+
}
|
|
8608
8752
|
function firstErrorMessage(evaluation) {
|
|
8609
8753
|
const e = evaluation?.findings.find((f) => f.level === "error");
|
|
8610
8754
|
return e ? e.message : null;
|
|
@@ -8715,7 +8859,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
8715
8859
|
const summary = gateSummary(stage, evaluation);
|
|
8716
8860
|
deps.sink?.recordStageGateEvaluated({
|
|
8717
8861
|
stageId: stage.id,
|
|
8718
|
-
gateId:
|
|
8862
|
+
gateId: gateKindOf(stage),
|
|
8719
8863
|
verdict: evaluation.passed ? "passed" : "failed",
|
|
8720
8864
|
evidence: evaluation.findings.map((f) => `[${f.level}] ${f.message}`).join(`
|
|
8721
8865
|
`),
|
|
@@ -8828,6 +8972,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
8828
8972
|
var TAG31 = "stage-advance", AGENT_LABEL = "agent";
|
|
8829
8973
|
var init_stage_advance = __esm(() => {
|
|
8830
8974
|
init_dist();
|
|
8975
|
+
init_gate_config_error();
|
|
8831
8976
|
init_log();
|
|
8832
8977
|
init_transitions();
|
|
8833
8978
|
});
|
|
@@ -11380,7 +11525,7 @@ __export(exports_worktree_gc, {
|
|
|
11380
11525
|
isTransientGitNetworkError: () => isTransientGitNetworkError,
|
|
11381
11526
|
WorktreeGc: () => WorktreeGc
|
|
11382
11527
|
});
|
|
11383
|
-
import { execFileSync as
|
|
11528
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
11384
11529
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
11385
11530
|
import { resolve as resolve3 } from "node:path";
|
|
11386
11531
|
function isTransientGitNetworkError(message) {
|
|
@@ -11448,7 +11593,7 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
11448
11593
|
}
|
|
11449
11594
|
}
|
|
11450
11595
|
try {
|
|
11451
|
-
|
|
11596
|
+
execFileSync11("git", ["worktree", "prune", "--expire=now"], {
|
|
11452
11597
|
cwd: repoRoot,
|
|
11453
11598
|
stdio: "pipe"
|
|
11454
11599
|
});
|
|
@@ -11479,7 +11624,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11479
11624
|
return result;
|
|
11480
11625
|
}
|
|
11481
11626
|
try {
|
|
11482
|
-
|
|
11627
|
+
execFileSync11("git", ["fetch", "--prune", "origin"], {
|
|
11483
11628
|
cwd: repoRoot,
|
|
11484
11629
|
stdio: "pipe",
|
|
11485
11630
|
...GIT_NETWORK_EXEC
|
|
@@ -11495,7 +11640,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11495
11640
|
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
11496
11641
|
let listing = "";
|
|
11497
11642
|
try {
|
|
11498
|
-
listing =
|
|
11643
|
+
listing = execFileSync11("git", [
|
|
11499
11644
|
"for-each-ref",
|
|
11500
11645
|
"--format=%(refname:strip=3) %(committerdate:unix)",
|
|
11501
11646
|
refPattern
|
|
@@ -11530,7 +11675,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11530
11675
|
break;
|
|
11531
11676
|
}
|
|
11532
11677
|
try {
|
|
11533
|
-
|
|
11678
|
+
execFileSync11("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
11534
11679
|
cwd: repoRoot,
|
|
11535
11680
|
stdio: "pipe",
|
|
11536
11681
|
...GIT_NETWORK_EXEC
|
|
@@ -11593,7 +11738,7 @@ class WorktreeGc {
|
|
|
11593
11738
|
}
|
|
11594
11739
|
function getRepoRoot2() {
|
|
11595
11740
|
try {
|
|
11596
|
-
return
|
|
11741
|
+
return execFileSync11("git", ["rev-parse", "--show-toplevel"], {
|
|
11597
11742
|
encoding: "utf-8"
|
|
11598
11743
|
}).trim();
|
|
11599
11744
|
} catch {
|
|
@@ -11634,12 +11779,12 @@ __export(exports_src, {
|
|
|
11634
11779
|
validatePrerequisites: () => validatePrerequisites,
|
|
11635
11780
|
main: () => main
|
|
11636
11781
|
});
|
|
11637
|
-
import { execFileSync as
|
|
11782
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
11638
11783
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11639
11784
|
import { createRequire as createRequire2 } from "node:module";
|
|
11640
11785
|
async function validatePrerequisites(config, banner) {
|
|
11641
11786
|
try {
|
|
11642
|
-
const ver =
|
|
11787
|
+
const ver = execFileSync12("claude", ["--version"], {
|
|
11643
11788
|
encoding: "utf-8"
|
|
11644
11789
|
}).trim();
|
|
11645
11790
|
banner.check(`Claude CLI ${ver}`);
|
|
@@ -11654,14 +11799,14 @@ async function validatePrerequisites(config, banner) {
|
|
|
11654
11799
|
validateGitProviderCli(provider);
|
|
11655
11800
|
}
|
|
11656
11801
|
try {
|
|
11657
|
-
const status =
|
|
11802
|
+
const status = execFileSync12("git", ["status", "--porcelain"], {
|
|
11658
11803
|
encoding: "utf-8"
|
|
11659
11804
|
}).trim();
|
|
11660
11805
|
if (status) {
|
|
11661
11806
|
banner.warn(`Working directory has uncommitted changes:
|
|
11662
11807
|
${status}`);
|
|
11663
11808
|
}
|
|
11664
|
-
|
|
11809
|
+
execFileSync12("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
|
|
11665
11810
|
encoding: "utf-8",
|
|
11666
11811
|
stdio: "pipe"
|
|
11667
11812
|
});
|