codeharness 0.32.1 → 0.32.2

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.2" : "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-IICSAAF4.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-IICSAAF4.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,
@@ -3912,10 +3917,16 @@ 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
3932
  info("Execution interrupted \u2014 saving state");
@@ -3924,107 +3935,121 @@ async function executeWorkflow(config) {
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
+ info(`[epic-${epicId}] Starting epic with ${epicItems.length} stories`);
3939
+ for (const step of config.workflow.epicFlow) {
3940
+ if (halted) break;
3941
+ if (config.abortSignal?.aborted) {
3942
+ state = { ...state, phase: "interrupted" };
3943
+ writeWorkflowState(state, projectDir);
3944
+ halted = true;
3945
+ break;
3946
+ }
3947
+ if (step === "story_flow") {
3948
+ for (const item of epicItems) {
3939
3949
  if (halted || config.abortSignal?.aborted) {
3950
+ if (config.abortSignal?.aborted) {
3951
+ state = { ...state, phase: "interrupted" };
3952
+ writeWorkflowState(state, projectDir);
3953
+ }
3940
3954
  halted = true;
3941
3955
  break;
3942
3956
  }
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)) {
3957
+ processedStories.add(item.key);
3958
+ for (const storyStep of config.workflow.storyFlow) {
3959
+ if (halted || config.abortSignal?.aborted) {
3975
3960
  halted = true;
3961
+ break;
3962
+ }
3963
+ if (typeof storyStep !== "string") continue;
3964
+ const taskName2 = storyStep;
3965
+ const task2 = config.workflow.tasks[taskName2];
3966
+ if (!task2) {
3967
+ warn(`workflow-engine: task "${taskName2}" not found, skipping`);
3968
+ continue;
3969
+ }
3970
+ if (task2.agent === null) continue;
3971
+ const definition2 = config.agents[task2.agent];
3972
+ if (!definition2) {
3973
+ warn(`workflow-engine: agent "${task2.agent}" not found for "${taskName2}"`);
3974
+ continue;
3975
+ }
3976
+ if (isTaskCompleted(state, taskName2, item.key)) continue;
3977
+ try {
3978
+ const dr = await dispatchTaskWithResult(task2, taskName2, item.key, definition2, state, config, void 0, lastOutputContract ?? void 0);
3979
+ state = dr.updatedState;
3980
+ lastOutputContract = dr.contract;
3981
+ propagateVerifyFlags(taskName2, dr.contract, projectDir);
3982
+ accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
3983
+ tasksCompleted++;
3984
+ } catch (err) {
3985
+ const engineError = handleDispatchError(err, taskName2, item.key);
3986
+ errors.push(engineError);
3987
+ if (config.onEvent) {
3988
+ config.onEvent({ type: "dispatch-error", taskName: taskName2, storyKey: item.key, error: { code: engineError.code, message: engineError.message } });
3989
+ } else {
3990
+ warn(`[${taskName2}] ${item.key} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
3991
+ }
3992
+ state = recordErrorInState(state, taskName2, item.key, engineError);
3993
+ writeWorkflowState(state, projectDir);
3994
+ if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
3995
+ halted = true;
3996
+ }
3997
+ break;
3976
3998
  }
3977
- break;
3978
3999
  }
3979
4000
  }
4001
+ continue;
3980
4002
  }
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;
4003
+ if (isLoopBlock(step)) {
4004
+ const loopResult = await executeLoopBlock(step, state, config, epicItems, lastOutputContract, storyFlowTasks);
4005
+ state = loopResult.state;
4006
+ errors.push(...loopResult.errors);
4007
+ tasksCompleted += loopResult.tasksCompleted;
4008
+ lastOutputContract = loopResult.lastContract;
4009
+ for (const item of epicItems) processedStories.add(item.key);
4010
+ if (loopResult.halted || state.phase === "max-iterations" || state.phase === "circuit-breaker") {
4011
+ halted = true;
4012
+ }
4013
+ continue;
3992
4014
  }
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}`);
4015
+ const taskName = step;
4016
+ const task = config.workflow.tasks[taskName];
4017
+ if (!task) {
4018
+ warn(`workflow-engine: task "${taskName}" not found, skipping`);
4019
+ continue;
4022
4020
  }
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;
4021
+ if (task.agent === null) continue;
4022
+ const definition = config.agents[task.agent];
4023
+ if (!definition) {
4024
+ warn(`workflow-engine: agent "${task.agent}" not found for "${taskName}"`);
4025
+ continue;
4027
4026
  }
4027
+ const epicSentinel = `__epic_${epicId}__`;
4028
+ if (isTaskCompleted(state, taskName, epicSentinel)) continue;
4029
+ try {
4030
+ const dr = await dispatchTaskWithResult(task, taskName, epicSentinel, definition, state, config, void 0, lastOutputContract ?? void 0);
4031
+ state = dr.updatedState;
4032
+ lastOutputContract = dr.contract;
4033
+ propagateVerifyFlags(taskName, dr.contract, projectDir);
4034
+ accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
4035
+ tasksCompleted++;
4036
+ } catch (err) {
4037
+ const engineError = handleDispatchError(err, taskName, epicSentinel);
4038
+ errors.push(engineError);
4039
+ if (config.onEvent) {
4040
+ config.onEvent({ type: "dispatch-error", taskName, storyKey: epicSentinel, error: { code: engineError.code, message: engineError.message } });
4041
+ } else {
4042
+ warn(`[${taskName}] epic-${epicId} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
4043
+ }
4044
+ state = recordErrorInState(state, taskName, epicSentinel, engineError);
4045
+ writeWorkflowState(state, projectDir);
4046
+ if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
4047
+ halted = true;
4048
+ }
4049
+ }
4050
+ }
4051
+ if (!halted) {
4052
+ info(`[epic-${epicId}] Epic completed`);
4028
4053
  }
4029
4054
  }
4030
4055
  if (state.phase === "interrupted") {
@@ -5468,7 +5493,7 @@ function StoryContext({ entries }) {
5468
5493
  if (entries.length === 0) return null;
5469
5494
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: entries.map((e, i) => {
5470
5495
  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);
5496
+ if (e.role === "current") return /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: ` This: ${e.key}` }) }, i);
5472
5497
  return /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` Next: ${e.key}` }) }, i);
5473
5498
  }) });
5474
5499
  }
@@ -11135,7 +11160,7 @@ function registerTeardownCommand(program) {
11135
11160
  } else if (otlpMode === "remote-routed") {
11136
11161
  if (!options.keepDocker) {
11137
11162
  try {
11138
- const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-NV4Q52NL.js");
11163
+ const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-GLX24TXX.js");
11139
11164
  stopCollectorOnly2();
11140
11165
  result.docker.stopped = true;
11141
11166
  if (!isJson) {
@@ -11167,7 +11192,7 @@ function registerTeardownCommand(program) {
11167
11192
  info("Shared stack: kept running (other projects may use it)");
11168
11193
  }
11169
11194
  } else if (isLegacyStack) {
11170
- const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-NV4Q52NL.js");
11195
+ const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-GLX24TXX.js");
11171
11196
  let stackRunning = false;
11172
11197
  try {
11173
11198
  stackRunning = isStackRunning2(composeFile);
@@ -14154,7 +14179,7 @@ function registerDriversCommand(program) {
14154
14179
  }
14155
14180
 
14156
14181
  // src/index.ts
14157
- var VERSION = true ? "0.32.1" : "0.0.0-dev";
14182
+ var VERSION = true ? "0.32.2" : "0.0.0-dev";
14158
14183
  function createProgram() {
14159
14184
  const program = new Command();
14160
14185
  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.2",
4
4
  "type": "module",
5
5
  "description": "CLI for codeharness — makes autonomous coding agents produce software that actually works",
6
6
  "bin": {