codeharness 0.29.1 → 0.29.3
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.
|
@@ -2895,7 +2895,7 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
|
|
|
2895
2895
|
}
|
|
2896
2896
|
|
|
2897
2897
|
// src/modules/infra/init-project.ts
|
|
2898
|
-
var HARNESS_VERSION = true ? "0.29.
|
|
2898
|
+
var HARNESS_VERSION = true ? "0.29.3" : "0.0.0-dev";
|
|
2899
2899
|
function failResult(opts, error) {
|
|
2900
2900
|
return {
|
|
2901
2901
|
status: "fail",
|
package/dist/index.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
validateDockerfile,
|
|
41
41
|
warn,
|
|
42
42
|
writeState
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-4QLSEKNP.js";
|
|
44
44
|
|
|
45
45
|
// src/index.ts
|
|
46
46
|
import { Command } from "commander";
|
|
@@ -3884,6 +3884,13 @@ async function executeWorkflow(config) {
|
|
|
3884
3884
|
let accumulatedCostUsd = 0;
|
|
3885
3885
|
for (const step of config.workflow.storyFlow) {
|
|
3886
3886
|
if (halted) break;
|
|
3887
|
+
if (config.abortSignal?.aborted) {
|
|
3888
|
+
info("Execution interrupted \u2014 saving state");
|
|
3889
|
+
state = { ...state, phase: "interrupted" };
|
|
3890
|
+
writeWorkflowState(state, projectDir);
|
|
3891
|
+
halted = true;
|
|
3892
|
+
break;
|
|
3893
|
+
}
|
|
3887
3894
|
if (isLoopBlock(step)) {
|
|
3888
3895
|
const loopResult = await executeLoopBlock(step, state, config, workItems, lastOutputContract);
|
|
3889
3896
|
state = loopResult.state;
|
|
@@ -4004,6 +4011,10 @@ async function executeWorkflow(config) {
|
|
|
4004
4011
|
}
|
|
4005
4012
|
} else {
|
|
4006
4013
|
for (const item of workItems) {
|
|
4014
|
+
if (config.abortSignal?.aborted) {
|
|
4015
|
+
halted = true;
|
|
4016
|
+
break;
|
|
4017
|
+
}
|
|
4007
4018
|
processedStories.add(item.key);
|
|
4008
4019
|
if (isTaskCompleted(state, taskName, item.key)) {
|
|
4009
4020
|
warn(`workflow-engine: skipping completed task ${taskName} for ${item.key}`);
|
|
@@ -4044,13 +4055,14 @@ async function executeWorkflow(config) {
|
|
|
4044
4055
|
}
|
|
4045
4056
|
}
|
|
4046
4057
|
}
|
|
4047
|
-
if (
|
|
4058
|
+
if (state.phase === "interrupted") {
|
|
4059
|
+
} else if (errors.length === 0 && state.phase !== "max-iterations" && state.phase !== "circuit-breaker") {
|
|
4048
4060
|
state = { ...state, phase: "completed" };
|
|
4049
4061
|
writeWorkflowState(state, projectDir);
|
|
4050
4062
|
}
|
|
4051
4063
|
const loopTerminated = state.phase === "max-iterations" || state.phase === "circuit-breaker";
|
|
4052
4064
|
return {
|
|
4053
|
-
success: errors.length === 0 && !loopTerminated,
|
|
4065
|
+
success: errors.length === 0 && !loopTerminated && state.phase !== "interrupted",
|
|
4054
4066
|
tasksCompleted,
|
|
4055
4067
|
storiesProcessed: processedStories.size,
|
|
4056
4068
|
errors,
|
|
@@ -6160,6 +6172,7 @@ function registerRunCommand(program) {
|
|
|
6160
6172
|
info("Resuming after circuit breaker \u2014 previous findings preserved", outputOpts);
|
|
6161
6173
|
}
|
|
6162
6174
|
}
|
|
6175
|
+
const abortController = new AbortController();
|
|
6163
6176
|
const renderer = startRenderer({
|
|
6164
6177
|
quiet: !!options.quiet || isJson,
|
|
6165
6178
|
sprintState: {
|
|
@@ -6170,6 +6183,34 @@ function registerRunCommand(program) {
|
|
|
6170
6183
|
totalCost: 0
|
|
6171
6184
|
}
|
|
6172
6185
|
});
|
|
6186
|
+
let interrupted = false;
|
|
6187
|
+
const onInterrupt = () => {
|
|
6188
|
+
if (interrupted) {
|
|
6189
|
+
renderer.cleanup();
|
|
6190
|
+
process.exit(1);
|
|
6191
|
+
}
|
|
6192
|
+
interrupted = true;
|
|
6193
|
+
abortController.abort();
|
|
6194
|
+
};
|
|
6195
|
+
process.on("SIGINT", onInterrupt);
|
|
6196
|
+
process.on("SIGTERM", onInterrupt);
|
|
6197
|
+
let totalCostUsd = 0;
|
|
6198
|
+
let storiesDone = counts.done;
|
|
6199
|
+
const taskStates = {};
|
|
6200
|
+
const taskMeta = {};
|
|
6201
|
+
for (const [tn, task] of Object.entries(parsedWorkflow.tasks)) {
|
|
6202
|
+
taskStates[tn] = "pending";
|
|
6203
|
+
taskMeta[tn] = { driver: task.driver ?? "claude-code" };
|
|
6204
|
+
}
|
|
6205
|
+
const storyEntries = [];
|
|
6206
|
+
for (const [key, status] of Object.entries(statuses)) {
|
|
6207
|
+
if (key.startsWith("epic-")) continue;
|
|
6208
|
+
if (status === "done") storyEntries.push({ key, status: "done" });
|
|
6209
|
+
else if (status === "in-progress") storyEntries.push({ key, status: "in-progress" });
|
|
6210
|
+
else if (status === "backlog" || status === "ready-for-dev") storyEntries.push({ key, status: "pending" });
|
|
6211
|
+
else if (status === "failed") storyEntries.push({ key, status: "failed" });
|
|
6212
|
+
}
|
|
6213
|
+
renderer.updateStories(storyEntries);
|
|
6173
6214
|
const onEvent = (event) => {
|
|
6174
6215
|
if (event.type === "stream-event" && event.streamEvent) {
|
|
6175
6216
|
renderer.update(event.streamEvent, event.driverName);
|
|
@@ -6177,24 +6218,39 @@ function registerRunCommand(program) {
|
|
|
6177
6218
|
if (event.type === "dispatch-start") {
|
|
6178
6219
|
renderer.updateSprintState({
|
|
6179
6220
|
storyKey: event.storyKey,
|
|
6180
|
-
phase:
|
|
6181
|
-
done:
|
|
6182
|
-
total: counts.total
|
|
6221
|
+
phase: event.taskName,
|
|
6222
|
+
done: storiesDone,
|
|
6223
|
+
total: counts.total,
|
|
6224
|
+
totalCost: totalCostUsd
|
|
6183
6225
|
});
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6226
|
+
taskStates[event.taskName] = "active";
|
|
6227
|
+
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
6228
|
+
const idx = storyEntries.findIndex((s) => s.key === event.storyKey);
|
|
6229
|
+
if (idx >= 0 && storyEntries[idx].status === "pending") {
|
|
6230
|
+
storyEntries[idx] = { ...storyEntries[idx], status: "in-progress" };
|
|
6231
|
+
renderer.updateStories([...storyEntries]);
|
|
6232
|
+
}
|
|
6189
6233
|
}
|
|
6190
6234
|
if (event.type === "dispatch-end") {
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6235
|
+
totalCostUsd += event.costUsd ?? 0;
|
|
6236
|
+
taskStates[event.taskName] = "done";
|
|
6237
|
+
taskMeta[event.taskName] = {
|
|
6238
|
+
...taskMeta[event.taskName],
|
|
6239
|
+
costUsd: (taskMeta[event.taskName]?.costUsd ?? 0) + (event.costUsd ?? 0),
|
|
6240
|
+
elapsedMs: (taskMeta[event.taskName]?.elapsedMs ?? 0) + (event.elapsedMs ?? 0)
|
|
6241
|
+
};
|
|
6242
|
+
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
6243
|
+
renderer.updateSprintState({
|
|
6244
|
+
storyKey: event.storyKey,
|
|
6245
|
+
phase: event.taskName,
|
|
6246
|
+
done: storiesDone,
|
|
6247
|
+
total: counts.total,
|
|
6248
|
+
totalCost: totalCostUsd
|
|
6249
|
+
});
|
|
6196
6250
|
}
|
|
6197
6251
|
if (event.type === "dispatch-error") {
|
|
6252
|
+
taskStates[event.taskName] = "failed";
|
|
6253
|
+
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
6198
6254
|
renderer.addMessage({
|
|
6199
6255
|
type: "fail",
|
|
6200
6256
|
key: event.storyKey,
|
|
@@ -6209,6 +6265,7 @@ function registerRunCommand(program) {
|
|
|
6209
6265
|
issuesPath: join15(projectDir, ".codeharness", "issues.yaml"),
|
|
6210
6266
|
runId: `run-${Date.now()}`,
|
|
6211
6267
|
projectDir,
|
|
6268
|
+
abortSignal: abortController.signal,
|
|
6212
6269
|
maxIterations,
|
|
6213
6270
|
onEvent
|
|
6214
6271
|
};
|
|
@@ -6288,8 +6345,13 @@ function registerRunCommand(program) {
|
|
|
6288
6345
|
} else {
|
|
6289
6346
|
try {
|
|
6290
6347
|
const result = await executeWorkflow(config);
|
|
6348
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
6349
|
+
process.removeListener("SIGTERM", onInterrupt);
|
|
6291
6350
|
renderer.cleanup();
|
|
6292
|
-
if (
|
|
6351
|
+
if (interrupted) {
|
|
6352
|
+
info(`Interrupted \u2014 ${result.storiesProcessed} stories processed, ${result.tasksCompleted} tasks completed. State saved \u2014 run again to resume.`, outputOpts);
|
|
6353
|
+
process.exitCode = 130;
|
|
6354
|
+
} else if (result.success) {
|
|
6293
6355
|
ok(`Workflow completed \u2014 ${result.storiesProcessed} stories processed, ${result.tasksCompleted} tasks completed in ${formatElapsed(result.durationMs)}`, outputOpts);
|
|
6294
6356
|
} else {
|
|
6295
6357
|
fail(`Workflow failed \u2014 ${result.storiesProcessed} stories processed, ${result.tasksCompleted} tasks completed, ${result.errors.length} error(s) in ${formatElapsed(result.durationMs)}`, outputOpts);
|
|
@@ -11083,7 +11145,7 @@ function registerTeardownCommand(program) {
|
|
|
11083
11145
|
} else if (otlpMode === "remote-routed") {
|
|
11084
11146
|
if (!options.keepDocker) {
|
|
11085
11147
|
try {
|
|
11086
|
-
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-
|
|
11148
|
+
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-XNAP7B5H.js");
|
|
11087
11149
|
stopCollectorOnly2();
|
|
11088
11150
|
result.docker.stopped = true;
|
|
11089
11151
|
if (!isJson) {
|
|
@@ -11115,7 +11177,7 @@ function registerTeardownCommand(program) {
|
|
|
11115
11177
|
info("Shared stack: kept running (other projects may use it)");
|
|
11116
11178
|
}
|
|
11117
11179
|
} else if (isLegacyStack) {
|
|
11118
|
-
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-
|
|
11180
|
+
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-XNAP7B5H.js");
|
|
11119
11181
|
let stackRunning = false;
|
|
11120
11182
|
try {
|
|
11121
11183
|
stackRunning = isStackRunning2(composeFile);
|
|
@@ -13993,7 +14055,7 @@ function registerDriversCommand(program) {
|
|
|
13993
14055
|
}
|
|
13994
14056
|
|
|
13995
14057
|
// src/index.ts
|
|
13996
|
-
var VERSION = true ? "0.29.
|
|
14058
|
+
var VERSION = true ? "0.29.3" : "0.0.0-dev";
|
|
13997
14059
|
function createProgram() {
|
|
13998
14060
|
const program = new Command();
|
|
13999
14061
|
program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");
|