codeharness 0.32.1 → 0.32.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.32.1" : "0.0.0-dev";
2898
+ var HARNESS_VERSION = true ? "0.32.3" : "0.0.0-dev";
2899
2899
  function failResult(opts, error) {
2900
2900
  return {
2901
2901
  status: "fail",
@@ -16,7 +16,7 @@ import {
16
16
  stopCollectorOnly,
17
17
  stopSharedStack,
18
18
  stopStack
19
- } from "./chunk-FW3FP7O7.js";
19
+ } from "./chunk-N57BYUXA.js";
20
20
  export {
21
21
  checkRemoteEndpoint,
22
22
  cleanupOrphanedContainers,
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  validateDockerfile,
41
41
  warn,
42
42
  writeState
43
- } from "./chunk-FW3FP7O7.js";
43
+ } from "./chunk-N57BYUXA.js";
44
44
 
45
45
  // src/index.ts
46
46
  import { Command } from "commander";
@@ -3360,12 +3360,17 @@ async function dispatchTaskWithResult(task, taskName, storyKey, definition, stat
3360
3360
  let cwd;
3361
3361
  let workspace = null;
3362
3362
  if (task.source_access === false) {
3363
- workspace = await createIsolatedWorkspace({ runId: config.runId, storyFiles: [] });
3364
- cwd = workspace.toDispatchOptions().cwd ?? projectDir;
3363
+ try {
3364
+ workspace = await createIsolatedWorkspace({ runId: config.runId, storyFiles: [] });
3365
+ cwd = workspace?.toDispatchOptions()?.cwd ?? projectDir;
3366
+ } catch {
3367
+ cwd = projectDir;
3368
+ }
3365
3369
  } else {
3366
3370
  cwd = projectDir;
3367
3371
  }
3368
- const basePrompt = customPrompt ?? (storyKey === PER_RUN_SENTINEL ? `Execute task "${taskName}" for the current run.` : `Implement story ${storyKey}`);
3372
+ const isEpicSentinel = storyKey.startsWith("__epic_") || storyKey === PER_RUN_SENTINEL;
3373
+ const basePrompt = customPrompt ?? (isEpicSentinel ? `Execute task "${taskName}" for the current run.` : `Implement story ${storyKey}`);
3369
3374
  let prompt = buildPromptWithContractContext(basePrompt, previousOutputContract ?? null);
3370
3375
  const coverageDedup = buildCoverageDeduplicationContext(
3371
3376
  previousOutputContract ?? null,
@@ -3873,7 +3878,7 @@ async function executeWorkflow(config) {
3873
3878
  }
3874
3879
  if (state.phase === "error" || state.phase === "failed") {
3875
3880
  const errorCount = state.tasks_completed.filter((t) => t.error).length;
3876
- info(`Resuming from ${state.phase} state \u2014 ${errorCount} previous error(s), retrying failed tasks`);
3881
+ if (!config.onEvent) info(`Resuming from ${state.phase} state \u2014 ${errorCount} previous error(s), retrying failed tasks`);
3877
3882
  }
3878
3883
  state = {
3879
3884
  ...state,
@@ -3912,120 +3917,144 @@ async function executeWorkflow(config) {
3912
3917
  for (const step of config.workflow.storyFlow) {
3913
3918
  if (typeof step === "string") storyFlowTasks.add(step);
3914
3919
  }
3920
+ const epicGroups = /* @__PURE__ */ new Map();
3921
+ for (const item of workItems) {
3922
+ const epicId = item.key.match(/^(\d+)-/)?.[1] ?? "unknown";
3923
+ if (!epicGroups.has(epicId)) epicGroups.set(epicId, []);
3924
+ epicGroups.get(epicId).push(item);
3925
+ }
3915
3926
  let halted = false;
3916
3927
  let lastOutputContract = null;
3917
3928
  let accumulatedCostUsd = 0;
3918
- for (const step of config.workflow.epicFlow) {
3929
+ for (const [epicId, epicItems] of epicGroups) {
3919
3930
  if (halted) break;
3920
3931
  if (config.abortSignal?.aborted) {
3921
- info("Execution interrupted \u2014 saving state");
3932
+ if (!config.onEvent) info("Execution interrupted \u2014 saving state");
3922
3933
  state = { ...state, phase: "interrupted" };
3923
3934
  writeWorkflowState(state, projectDir);
3924
3935
  halted = true;
3925
3936
  break;
3926
3937
  }
3927
- if (step === "story_flow") {
3928
- for (const item of workItems) {
3929
- if (halted || config.abortSignal?.aborted) {
3930
- if (config.abortSignal?.aborted) {
3931
- state = { ...state, phase: "interrupted" };
3932
- writeWorkflowState(state, projectDir);
3933
- }
3934
- halted = true;
3935
- break;
3936
- }
3937
- processedStories.add(item.key);
3938
- for (const storyStep of config.workflow.storyFlow) {
3938
+ if (config.onEvent) {
3939
+ config.onEvent({ type: "dispatch-start", taskName: "story_flow", storyKey: `__epic_${epicId}__` });
3940
+ } else {
3941
+ info(`[epic-${epicId}] Starting epic with ${epicItems.length} stories`);
3942
+ }
3943
+ for (const step of config.workflow.epicFlow) {
3944
+ if (halted) break;
3945
+ if (config.abortSignal?.aborted) {
3946
+ state = { ...state, phase: "interrupted" };
3947
+ writeWorkflowState(state, projectDir);
3948
+ halted = true;
3949
+ break;
3950
+ }
3951
+ if (step === "story_flow") {
3952
+ for (const item of epicItems) {
3939
3953
  if (halted || config.abortSignal?.aborted) {
3954
+ if (config.abortSignal?.aborted) {
3955
+ state = { ...state, phase: "interrupted" };
3956
+ writeWorkflowState(state, projectDir);
3957
+ }
3940
3958
  halted = true;
3941
3959
  break;
3942
3960
  }
3943
- if (typeof storyStep !== "string") continue;
3944
- const taskName2 = storyStep;
3945
- const task2 = config.workflow.tasks[taskName2];
3946
- if (!task2) {
3947
- warn(`workflow-engine: task "${taskName2}" not found, skipping`);
3948
- continue;
3949
- }
3950
- if (task2.agent === null) continue;
3951
- const definition2 = config.agents[task2.agent];
3952
- if (!definition2) {
3953
- warn(`workflow-engine: agent "${task2.agent}" not found for "${taskName2}"`);
3954
- continue;
3955
- }
3956
- if (isTaskCompleted(state, taskName2, item.key)) continue;
3957
- try {
3958
- const dr = await dispatchTaskWithResult(task2, taskName2, item.key, definition2, state, config, void 0, lastOutputContract ?? void 0);
3959
- state = dr.updatedState;
3960
- lastOutputContract = dr.contract;
3961
- propagateVerifyFlags(taskName2, dr.contract, projectDir);
3962
- accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
3963
- tasksCompleted++;
3964
- } catch (err) {
3965
- const engineError = handleDispatchError(err, taskName2, item.key);
3966
- errors.push(engineError);
3967
- if (config.onEvent) {
3968
- config.onEvent({ type: "dispatch-error", taskName: taskName2, storyKey: item.key, error: { code: engineError.code, message: engineError.message } });
3969
- } else {
3970
- warn(`[${taskName2}] ${item.key} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
3971
- }
3972
- state = recordErrorInState(state, taskName2, item.key, engineError);
3973
- writeWorkflowState(state, projectDir);
3974
- if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
3961
+ processedStories.add(item.key);
3962
+ for (const storyStep of config.workflow.storyFlow) {
3963
+ if (halted || config.abortSignal?.aborted) {
3975
3964
  halted = true;
3965
+ break;
3966
+ }
3967
+ if (typeof storyStep !== "string") continue;
3968
+ const taskName2 = storyStep;
3969
+ const task2 = config.workflow.tasks[taskName2];
3970
+ if (!task2) {
3971
+ warn(`workflow-engine: task "${taskName2}" not found, skipping`);
3972
+ continue;
3973
+ }
3974
+ if (task2.agent === null) continue;
3975
+ const definition2 = config.agents[task2.agent];
3976
+ if (!definition2) {
3977
+ warn(`workflow-engine: agent "${task2.agent}" not found for "${taskName2}"`);
3978
+ continue;
3979
+ }
3980
+ if (isTaskCompleted(state, taskName2, item.key)) continue;
3981
+ try {
3982
+ const dr = await dispatchTaskWithResult(task2, taskName2, item.key, definition2, state, config, void 0, lastOutputContract ?? void 0);
3983
+ state = dr.updatedState;
3984
+ lastOutputContract = dr.contract;
3985
+ propagateVerifyFlags(taskName2, dr.contract, projectDir);
3986
+ accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
3987
+ tasksCompleted++;
3988
+ } catch (err) {
3989
+ const engineError = handleDispatchError(err, taskName2, item.key);
3990
+ errors.push(engineError);
3991
+ if (config.onEvent) {
3992
+ config.onEvent({ type: "dispatch-error", taskName: taskName2, storyKey: item.key, error: { code: engineError.code, message: engineError.message } });
3993
+ } else {
3994
+ warn(`[${taskName2}] ${item.key} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
3995
+ }
3996
+ state = recordErrorInState(state, taskName2, item.key, engineError);
3997
+ writeWorkflowState(state, projectDir);
3998
+ if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
3999
+ halted = true;
4000
+ }
4001
+ break;
3976
4002
  }
3977
- break;
3978
4003
  }
3979
4004
  }
4005
+ continue;
3980
4006
  }
3981
- continue;
3982
- }
3983
- if (isLoopBlock(step)) {
3984
- const loopResult = await executeLoopBlock(step, state, config, workItems, lastOutputContract, storyFlowTasks);
3985
- state = loopResult.state;
3986
- errors.push(...loopResult.errors);
3987
- tasksCompleted += loopResult.tasksCompleted;
3988
- lastOutputContract = loopResult.lastContract;
3989
- for (const item of workItems) processedStories.add(item.key);
3990
- if (loopResult.halted || state.phase === "max-iterations" || state.phase === "circuit-breaker") {
3991
- halted = true;
4007
+ if (isLoopBlock(step)) {
4008
+ const loopResult = await executeLoopBlock(step, state, config, epicItems, lastOutputContract, storyFlowTasks);
4009
+ state = loopResult.state;
4010
+ errors.push(...loopResult.errors);
4011
+ tasksCompleted += loopResult.tasksCompleted;
4012
+ lastOutputContract = loopResult.lastContract;
4013
+ for (const item of epicItems) processedStories.add(item.key);
4014
+ if (loopResult.halted || state.phase === "max-iterations" || state.phase === "circuit-breaker") {
4015
+ halted = true;
4016
+ }
4017
+ continue;
3992
4018
  }
3993
- continue;
3994
- }
3995
- const taskName = step;
3996
- const task = config.workflow.tasks[taskName];
3997
- if (!task) {
3998
- warn(`workflow-engine: task "${taskName}" not found, skipping`);
3999
- continue;
4000
- }
4001
- if (task.agent === null) continue;
4002
- const definition = config.agents[task.agent];
4003
- if (!definition) {
4004
- warn(`workflow-engine: agent "${task.agent}" not found for "${taskName}"`);
4005
- continue;
4006
- }
4007
- if (isTaskCompleted(state, taskName, PER_RUN_SENTINEL)) continue;
4008
- try {
4009
- const dr = await dispatchTaskWithResult(task, taskName, PER_RUN_SENTINEL, definition, state, config, void 0, lastOutputContract ?? void 0);
4010
- state = dr.updatedState;
4011
- lastOutputContract = dr.contract;
4012
- propagateVerifyFlags(taskName, dr.contract, projectDir);
4013
- accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
4014
- tasksCompleted++;
4015
- } catch (err) {
4016
- const engineError = handleDispatchError(err, taskName, PER_RUN_SENTINEL);
4017
- errors.push(engineError);
4018
- if (config.onEvent) {
4019
- config.onEvent({ type: "dispatch-error", taskName, storyKey: PER_RUN_SENTINEL, error: { code: engineError.code, message: engineError.message } });
4020
- } else {
4021
- warn(`[${taskName}] ${PER_RUN_SENTINEL} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
4019
+ const taskName = step;
4020
+ const task = config.workflow.tasks[taskName];
4021
+ if (!task) {
4022
+ warn(`workflow-engine: task "${taskName}" not found, skipping`);
4023
+ continue;
4022
4024
  }
4023
- state = recordErrorInState(state, taskName, PER_RUN_SENTINEL, engineError);
4024
- writeWorkflowState(state, projectDir);
4025
- if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
4026
- halted = true;
4025
+ if (task.agent === null) continue;
4026
+ const definition = config.agents[task.agent];
4027
+ if (!definition) {
4028
+ warn(`workflow-engine: agent "${task.agent}" not found for "${taskName}"`);
4029
+ continue;
4030
+ }
4031
+ const epicSentinel = `__epic_${epicId}__`;
4032
+ if (isTaskCompleted(state, taskName, epicSentinel)) continue;
4033
+ try {
4034
+ const dr = await dispatchTaskWithResult(task, taskName, epicSentinel, definition, state, config, void 0, lastOutputContract ?? void 0);
4035
+ state = dr.updatedState;
4036
+ lastOutputContract = dr.contract;
4037
+ propagateVerifyFlags(taskName, dr.contract, projectDir);
4038
+ accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
4039
+ tasksCompleted++;
4040
+ } catch (err) {
4041
+ const engineError = handleDispatchError(err, taskName, epicSentinel);
4042
+ errors.push(engineError);
4043
+ if (config.onEvent) {
4044
+ config.onEvent({ type: "dispatch-error", taskName, storyKey: epicSentinel, error: { code: engineError.code, message: engineError.message } });
4045
+ } else {
4046
+ warn(`[${taskName}] epic-${epicId} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
4047
+ }
4048
+ state = recordErrorInState(state, taskName, epicSentinel, engineError);
4049
+ writeWorkflowState(state, projectDir);
4050
+ if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
4051
+ halted = true;
4052
+ }
4027
4053
  }
4028
4054
  }
4055
+ if (!halted) {
4056
+ if (!config.onEvent) info(`[epic-${epicId}] Epic completed`);
4057
+ }
4029
4058
  }
4030
4059
  if (state.phase === "interrupted") {
4031
4060
  } else if (errors.length === 0 && state.phase !== "max-iterations" && state.phase !== "circuit-breaker") {
@@ -5468,7 +5497,7 @@ function StoryContext({ entries }) {
5468
5497
  if (entries.length === 0) return null;
5469
5498
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: entries.map((e, i) => {
5470
5499
  if (e.role === "prev") return /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: "green", children: ` Prev: ${e.key} \u2713` }) }, i);
5471
- if (e.role === "current") return /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: ` This: ${e.key} \u25C6 ${e.task ?? ""}` }) }, i);
5500
+ if (e.role === "current") return /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: ` This: ${e.key}` }) }, i);
5472
5501
  return /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` Next: ${e.key}` }) }, i);
5473
5502
  }) });
5474
5503
  }
@@ -6214,17 +6243,17 @@ function registerRunCommand(program) {
6214
6243
  total: counts.total,
6215
6244
  totalCost: totalCostUsd
6216
6245
  });
6217
- if (storyFlowTasks.has(event.taskName)) {
6218
- const allStoryDone = [...storyFlowTasks].every((tn) => taskStates[tn] === "done");
6219
- if (allStoryDone) {
6220
- storiesDone++;
6221
- updateStoryStatus2(event.storyKey, "done");
6222
- const idx = storyEntries.findIndex((s) => s.key === event.storyKey);
6223
- if (idx >= 0) {
6224
- storyEntries[idx] = { ...storyEntries[idx], status: "done" };
6225
- renderer.updateStories([...storyEntries]);
6246
+ if (event.taskName === "verify" && event.storyKey.startsWith("__epic_")) {
6247
+ const epicId = event.storyKey.replace("__epic_", "").replace("__", "");
6248
+ for (let i = 0; i < storyEntries.length; i++) {
6249
+ const se = storyEntries[i];
6250
+ if (se.status === "in-progress" && se.key.startsWith(`${epicId}-`)) {
6251
+ storiesDone++;
6252
+ updateStoryStatus2(se.key, "done");
6253
+ storyEntries[i] = { ...se, status: "done" };
6226
6254
  }
6227
6255
  }
6256
+ renderer.updateStories([...storyEntries]);
6228
6257
  }
6229
6258
  }
6230
6259
  if (event.type === "dispatch-error") {
@@ -11135,7 +11164,7 @@ function registerTeardownCommand(program) {
11135
11164
  } else if (otlpMode === "remote-routed") {
11136
11165
  if (!options.keepDocker) {
11137
11166
  try {
11138
- const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-NV4Q52NL.js");
11167
+ const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-UY37PFPB.js");
11139
11168
  stopCollectorOnly2();
11140
11169
  result.docker.stopped = true;
11141
11170
  if (!isJson) {
@@ -11167,7 +11196,7 @@ function registerTeardownCommand(program) {
11167
11196
  info("Shared stack: kept running (other projects may use it)");
11168
11197
  }
11169
11198
  } else if (isLegacyStack) {
11170
- const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-NV4Q52NL.js");
11199
+ const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-UY37PFPB.js");
11171
11200
  let stackRunning = false;
11172
11201
  try {
11173
11202
  stackRunning = isStackRunning2(composeFile);
@@ -14154,7 +14183,7 @@ function registerDriversCommand(program) {
14154
14183
  }
14155
14184
 
14156
14185
  // src/index.ts
14157
- var VERSION = true ? "0.32.1" : "0.0.0-dev";
14186
+ var VERSION = true ? "0.32.3" : "0.0.0-dev";
14158
14187
  function createProgram() {
14159
14188
  const program = new Command();
14160
14189
  program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeharness",
3
- "version": "0.32.1",
3
+ "version": "0.32.3",
4
4
  "type": "module",
5
5
  "description": "CLI for codeharness — makes autonomous coding agents produce software that actually works",
6
6
  "bin": {