codeharness 0.31.1 → 0.31.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.31.1" : "0.0.0-dev";
2898
+ var HARNESS_VERSION = true ? "0.31.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-INMK5DZS.js";
19
+ } from "./chunk-A4ECRBVK.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-INMK5DZS.js";
43
+ } from "./chunk-A4ECRBVK.js";
44
44
 
45
45
  // src/index.ts
46
46
  import { Command } from "commander";
@@ -5036,6 +5036,13 @@ function StoryMessageLine({ msg }) {
5036
5036
  ] });
5037
5037
  }
5038
5038
  function CompletedTool({ entry }) {
5039
+ if (entry.isText) {
5040
+ const text = entry.args.length > 80 ? entry.args.slice(0, 80) + "\u2026" : entry.args;
5041
+ return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
5042
+ /* @__PURE__ */ jsx(Text, { children: "\u{1F4AD} " }),
5043
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: text })
5044
+ ] });
5045
+ }
5039
5046
  const argsSummary = entry.args.length > 60 ? entry.args.slice(0, 60) + "\u2026" : entry.args;
5040
5047
  return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
5041
5048
  /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 " }),
@@ -5105,12 +5112,24 @@ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834"
5105
5112
  function isLoopBlock2(step) {
5106
5113
  return typeof step === "object" && step !== null && "loop" in step;
5107
5114
  }
5108
- function TaskNode({ name, status, spinnerFrame }) {
5115
+ function driverLabel(driver) {
5116
+ if (!driver) return "";
5117
+ if (driver.includes("opus")) return "opus";
5118
+ if (driver.includes("sonnet")) return "snnt";
5119
+ if (driver.includes("haiku")) return "haiku";
5120
+ if (driver === "codex" || driver === "codex-mini") return "cdx";
5121
+ if (driver === "claude-code") return "cc";
5122
+ if (driver === "opencode") return "oc";
5123
+ return driver.slice(0, 4);
5124
+ }
5125
+ function TaskNode({ name, status, spinnerFrame, driver }) {
5109
5126
  const s = status ?? "pending";
5127
+ const tag = driver ? ` [${driverLabel(driver)}]` : "";
5110
5128
  switch (s) {
5111
5129
  case "done":
5112
5130
  return /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
5113
5131
  name,
5132
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: tag }),
5114
5133
  " \u2713"
5115
5134
  ] });
5116
5135
  case "active": {
@@ -5118,17 +5137,22 @@ function TaskNode({ name, status, spinnerFrame }) {
5118
5137
  return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
5119
5138
  frame,
5120
5139
  " ",
5121
- name
5140
+ name,
5141
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: tag })
5122
5142
  ] });
5123
5143
  }
5124
5144
  case "failed":
5125
5145
  return /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
5126
5146
  name,
5147
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: tag }),
5127
5148
  " \u2717"
5128
5149
  ] });
5129
5150
  case "pending":
5130
5151
  default:
5131
- return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: name });
5152
+ return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
5153
+ name,
5154
+ tag
5155
+ ] });
5132
5156
  }
5133
5157
  }
5134
5158
  function loopIteration(tasks, taskStates) {
@@ -5138,49 +5162,77 @@ function loopIteration(tasks, taskStates) {
5138
5162
  });
5139
5163
  return anyStarted ? 1 : 0;
5140
5164
  }
5141
- function hasMetaData(taskMeta) {
5142
- if (!taskMeta) return false;
5143
- return Object.keys(taskMeta).length > 0;
5144
- }
5145
5165
  function WorkflowGraph({ flow, currentTask, taskStates, taskMeta }) {
5146
5166
  if (flow.length === 0 || Object.keys(taskStates).length === 0) {
5147
5167
  return null;
5148
5168
  }
5149
5169
  const meta = taskMeta ?? {};
5150
- const showMeta = hasMetaData(taskMeta);
5151
5170
  const spinnerFrame = Math.floor(Date.now() / 80);
5152
- const elements = [];
5153
- for (let i = 0; i < flow.length; i++) {
5154
- const step = flow[i];
5155
- if (i > 0) {
5156
- elements.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `arrow-${i}`));
5157
- }
5171
+ let inLoop = false;
5172
+ let loopBlock = null;
5173
+ let loopItCount = 0;
5174
+ for (const step of flow) {
5158
5175
  if (isLoopBlock2(step)) {
5159
- const iteration = loopIteration(step.loop, taskStates);
5160
- const loopNodes = [];
5161
- for (let j = 0; j < step.loop.length; j++) {
5162
- if (j > 0) {
5163
- loopNodes.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `loop-arrow-${i}-${j}`));
5164
- }
5165
- loopNodes.push(
5166
- /* @__PURE__ */ jsx2(TaskNode, { name: step.loop[j], status: taskStates[step.loop[j]], spinnerFrame }, `loop-task-${i}-${j}`)
5176
+ loopBlock = step;
5177
+ loopItCount = loopIteration(step.loop, taskStates);
5178
+ inLoop = loopItCount > 0;
5179
+ break;
5180
+ }
5181
+ }
5182
+ if (inLoop && loopBlock) {
5183
+ const elements2 = [];
5184
+ for (const step of flow) {
5185
+ if (isLoopBlock2(step)) break;
5186
+ if (typeof step === "string") {
5187
+ if (elements2.length > 0) elements2.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `a-${step}`));
5188
+ elements2.push(
5189
+ /* @__PURE__ */ jsx2(TaskNode, { name: step, status: taskStates[step], spinnerFrame, driver: meta[step]?.driver }, `t-${step}`)
5167
5190
  );
5168
5191
  }
5169
- elements.push(
5170
- /* @__PURE__ */ jsxs2(Text2, { children: [
5171
- /* @__PURE__ */ jsxs2(Text2, { children: [
5172
- "loop(",
5173
- iteration,
5174
- ")[ "
5175
- ] }),
5176
- loopNodes,
5177
- /* @__PURE__ */ jsx2(Text2, { children: " ]" })
5178
- ] }, `loop-${i}`)
5192
+ }
5193
+ if (elements2.length > 0) elements2.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, "loop-arrow"));
5194
+ elements2.push(/* @__PURE__ */ jsx2(Text2, { children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: `Loop ${loopItCount}: ` }) }, "loop-label"));
5195
+ for (let j = 0; j < loopBlock.loop.length; j++) {
5196
+ if (j > 0) elements2.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `la-${j}`));
5197
+ const tn = loopBlock.loop[j];
5198
+ const loopKey = `loop:${tn}`;
5199
+ const status = taskStates[loopKey] ?? taskStates[tn];
5200
+ const driver = meta[loopKey]?.driver ?? meta[tn]?.driver;
5201
+ elements2.push(
5202
+ /* @__PURE__ */ jsx2(TaskNode, { name: tn, status, spinnerFrame, driver }, `lt-${j}`)
5179
5203
  );
5180
- } else {
5204
+ }
5205
+ const loopDone = loopBlock.loop.every((t) => (taskStates[`loop:${t}`] ?? taskStates[t]) === "done");
5206
+ if (loopDone) {
5207
+ let afterLoop = false;
5208
+ for (const step of flow) {
5209
+ if (afterLoop && typeof step === "string") {
5210
+ elements2.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `post-a-${step}`));
5211
+ elements2.push(
5212
+ /* @__PURE__ */ jsx2(TaskNode, { name: step, status: taskStates[step], spinnerFrame, driver: meta[step]?.driver }, `post-${step}`)
5213
+ );
5214
+ }
5215
+ if (isLoopBlock2(step)) afterLoop = true;
5216
+ }
5217
+ }
5218
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { children: [
5219
+ " ",
5220
+ elements2
5221
+ ] }) });
5222
+ }
5223
+ const elements = [];
5224
+ let passedCurrent = false;
5225
+ for (const step of flow) {
5226
+ if (isLoopBlock2(step)) break;
5227
+ if (typeof step === "string") {
5228
+ if (elements.length > 0) {
5229
+ elements.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `a-${step}`));
5230
+ }
5181
5231
  elements.push(
5182
- /* @__PURE__ */ jsx2(TaskNode, { name: step, status: taskStates[step], spinnerFrame }, `task-${i}`)
5232
+ /* @__PURE__ */ jsx2(TaskNode, { name: step, status: taskStates[step], spinnerFrame, driver: meta[step]?.driver }, `t-${step}`)
5183
5233
  );
5234
+ if (step === currentTask) passedCurrent = true;
5235
+ if (passedCurrent && step !== currentTask) break;
5184
5236
  }
5185
5237
  }
5186
5238
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { children: [
@@ -5779,6 +5831,11 @@ function startRenderer(options) {
5779
5831
  }
5780
5832
  break;
5781
5833
  case "text":
5834
+ if (state.lastThought) {
5835
+ const textEntry = { name: "", args: state.lastThought, isText: true };
5836
+ const updated = [...state.completedTools, textEntry];
5837
+ state.completedTools = updated.length > MAX_COMPLETED_TOOLS ? updated.slice(updated.length - MAX_COMPLETED_TOOLS) : updated;
5838
+ }
5782
5839
  state.lastThought = event.text;
5783
5840
  state.retryInfo = null;
5784
5841
  break;
@@ -6209,11 +6266,32 @@ function registerRunCommand(program) {
6209
6266
  epicData[epicId] = { storiesDone: epic.storiesDone ?? 0, storiesTotal: epic.storiesTotal ?? 0 };
6210
6267
  }
6211
6268
  }
6269
+ const preLoopTasks = /* @__PURE__ */ new Set();
6270
+ const loopTasks = /* @__PURE__ */ new Set();
6271
+ for (const step of parsedWorkflow.flow) {
6272
+ if (typeof step === "string") {
6273
+ preLoopTasks.add(step);
6274
+ } else if (typeof step === "object" && "loop" in step) {
6275
+ for (const lt of step.loop) loopTasks.add(lt);
6276
+ }
6277
+ }
6278
+ let pastLoop = false;
6279
+ for (const step of parsedWorkflow.flow) {
6280
+ if (typeof step === "object" && "loop" in step) {
6281
+ pastLoop = true;
6282
+ continue;
6283
+ }
6284
+ if (pastLoop && typeof step === "string") preLoopTasks.add(step);
6285
+ }
6286
+ let inLoop = false;
6212
6287
  const taskStates = {};
6213
6288
  const taskMeta = {};
6214
6289
  for (const [tn, task] of Object.entries(parsedWorkflow.tasks)) {
6215
6290
  taskStates[tn] = "pending";
6216
- taskMeta[tn] = { driver: task.driver ?? "claude-code" };
6291
+ if (loopTasks.has(tn)) taskStates[`loop:${tn}`] = "pending";
6292
+ const driverLabel2 = task.model ?? task.driver ?? "claude-code";
6293
+ taskMeta[tn] = { driver: driverLabel2 };
6294
+ if (loopTasks.has(tn)) taskMeta[`loop:${tn}`] = { driver: driverLabel2 };
6217
6295
  }
6218
6296
  const storyEntries = [];
6219
6297
  for (const [key, status] of Object.entries(statuses)) {
@@ -6229,8 +6307,18 @@ function registerRunCommand(program) {
6229
6307
  renderer.update(event.streamEvent, event.driverName);
6230
6308
  }
6231
6309
  if (event.type === "dispatch-start") {
6310
+ if (event.storyKey !== currentStoryKey && preLoopTasks.has(event.taskName)) {
6311
+ inLoop = false;
6312
+ for (const tn of Object.keys(taskStates)) {
6313
+ taskStates[tn] = "pending";
6314
+ }
6315
+ }
6232
6316
  currentStoryKey = event.storyKey;
6233
6317
  currentTaskName = event.taskName;
6318
+ if (loopTasks.has(event.taskName) && taskStates[event.taskName] === "done") {
6319
+ inLoop = true;
6320
+ }
6321
+ const stateKey = inLoop && loopTasks.has(event.taskName) ? `loop:${event.taskName}` : event.taskName;
6234
6322
  const epicId = extractEpicId2(event.storyKey);
6235
6323
  const epic = epicData[epicId];
6236
6324
  renderer.updateSprintState({
@@ -6244,7 +6332,7 @@ function registerRunCommand(program) {
6244
6332
  epicStoriesDone: epic?.storiesDone ?? 0,
6245
6333
  epicStoriesTotal: epic?.storiesTotal ?? 0
6246
6334
  });
6247
- taskStates[event.taskName] = "active";
6335
+ taskStates[stateKey] = "active";
6248
6336
  renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
6249
6337
  const idx = storyEntries.findIndex((s) => s.key === event.storyKey);
6250
6338
  if (idx >= 0 && storyEntries[idx].status === "pending") {
@@ -6254,11 +6342,12 @@ function registerRunCommand(program) {
6254
6342
  }
6255
6343
  if (event.type === "dispatch-end") {
6256
6344
  totalCostUsd += event.costUsd ?? 0;
6257
- taskStates[event.taskName] = "done";
6258
- taskMeta[event.taskName] = {
6259
- ...taskMeta[event.taskName],
6260
- costUsd: (taskMeta[event.taskName]?.costUsd ?? 0) + (event.costUsd ?? 0),
6261
- elapsedMs: (taskMeta[event.taskName]?.elapsedMs ?? 0) + (event.elapsedMs ?? 0)
6345
+ const stateKey = inLoop && loopTasks.has(event.taskName) ? `loop:${event.taskName}` : event.taskName;
6346
+ taskStates[stateKey] = "done";
6347
+ taskMeta[stateKey] = {
6348
+ ...taskMeta[stateKey],
6349
+ costUsd: (taskMeta[stateKey]?.costUsd ?? 0) + (event.costUsd ?? 0),
6350
+ elapsedMs: (taskMeta[stateKey]?.elapsedMs ?? 0) + (event.elapsedMs ?? 0)
6262
6351
  };
6263
6352
  renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
6264
6353
  renderer.updateSprintState({
@@ -6270,7 +6359,8 @@ function registerRunCommand(program) {
6270
6359
  });
6271
6360
  }
6272
6361
  if (event.type === "dispatch-error") {
6273
- taskStates[event.taskName] = "failed";
6362
+ const stateKey = inLoop && loopTasks.has(event.taskName) ? `loop:${event.taskName}` : event.taskName;
6363
+ taskStates[stateKey] = "failed";
6274
6364
  renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
6275
6365
  renderer.addMessage({
6276
6366
  type: "fail",
@@ -11169,7 +11259,7 @@ function registerTeardownCommand(program) {
11169
11259
  } else if (otlpMode === "remote-routed") {
11170
11260
  if (!options.keepDocker) {
11171
11261
  try {
11172
- const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-2Z4EIH3U.js");
11262
+ const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-A35UFVYH.js");
11173
11263
  stopCollectorOnly2();
11174
11264
  result.docker.stopped = true;
11175
11265
  if (!isJson) {
@@ -11201,7 +11291,7 @@ function registerTeardownCommand(program) {
11201
11291
  info("Shared stack: kept running (other projects may use it)");
11202
11292
  }
11203
11293
  } else if (isLegacyStack) {
11204
- const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-2Z4EIH3U.js");
11294
+ const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-A35UFVYH.js");
11205
11295
  let stackRunning = false;
11206
11296
  try {
11207
11297
  stackRunning = isStackRunning2(composeFile);
@@ -13641,6 +13731,68 @@ function classifyError2(err) {
13641
13731
  }
13642
13732
  return "UNKNOWN";
13643
13733
  }
13734
+ function parseLineMulti(line) {
13735
+ const trimmed = line.trim();
13736
+ if (trimmed.length === 0) return [];
13737
+ let parsed;
13738
+ try {
13739
+ parsed = JSON.parse(trimmed);
13740
+ } catch {
13741
+ return [];
13742
+ }
13743
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
13744
+ const type = parsed.type;
13745
+ const item = parsed.item;
13746
+ if (type === "item.started" && item) {
13747
+ const itemType = item.type;
13748
+ if (itemType === "command_execution") {
13749
+ const cmd = item.command ?? "";
13750
+ return [
13751
+ { type: "tool-start", name: "Bash", id: item.id ?? "" },
13752
+ { type: "tool-input", partial: cmd }
13753
+ ];
13754
+ }
13755
+ if (itemType === "file_edit") {
13756
+ const path = item.file_path ?? item.path ?? "";
13757
+ return [
13758
+ { type: "tool-start", name: "Edit", id: item.id ?? "" },
13759
+ { type: "tool-input", partial: path }
13760
+ ];
13761
+ }
13762
+ if (itemType === "file_read") {
13763
+ const path = item.file_path ?? item.path ?? "";
13764
+ return [
13765
+ { type: "tool-start", name: "Read", id: item.id ?? "" },
13766
+ { type: "tool-input", partial: path }
13767
+ ];
13768
+ }
13769
+ return [];
13770
+ }
13771
+ if (type === "item.completed" && item) {
13772
+ const itemType = item.type;
13773
+ if (itemType === "command_execution") return [{ type: "tool-complete" }];
13774
+ if (itemType === "agent_message") {
13775
+ const text = item.text;
13776
+ return text ? [{ type: "text", text }] : [];
13777
+ }
13778
+ if (itemType === "file_edit" || itemType === "file_read") return [{ type: "tool-complete" }];
13779
+ return [];
13780
+ }
13781
+ if (type === "turn.completed") {
13782
+ const usage = parsed.usage;
13783
+ if (usage) {
13784
+ return [{
13785
+ type: "result",
13786
+ cost: 0,
13787
+ sessionId: "",
13788
+ cost_usd: null
13789
+ }];
13790
+ }
13791
+ return [];
13792
+ }
13793
+ const legacy = parseLine(line);
13794
+ return legacy ? [legacy] : [];
13795
+ }
13644
13796
  function parseLine(line) {
13645
13797
  const trimmed = line.trim();
13646
13798
  if (trimmed.length === 0) return null;
@@ -13772,7 +13924,7 @@ var CodexDriver = class {
13772
13924
  opts.plugins
13773
13925
  );
13774
13926
  }
13775
- const args = ["exec", "--json"];
13927
+ const args = ["exec", "--json", "--full-auto"];
13776
13928
  const model = opts.model && !opts.model.startsWith("claude-") ? opts.model : void 0;
13777
13929
  if (model) {
13778
13930
  args.push("--model", model);
@@ -13803,8 +13955,8 @@ var CodexDriver = class {
13803
13955
  });
13804
13956
  try {
13805
13957
  for await (const line of rl) {
13806
- const event = parseLine(line);
13807
- if (event) {
13958
+ const events = parseLineMulti(line);
13959
+ for (const event of events) {
13808
13960
  if (event.type === "result") {
13809
13961
  const resultEvent = event;
13810
13962
  if (typeof resultEvent.cost_usd === "number") {
@@ -13815,8 +13967,6 @@ var CodexDriver = class {
13815
13967
  } else {
13816
13968
  yield event;
13817
13969
  }
13818
- } else {
13819
- console.debug("[CodexDriver] Skipping unparseable line:", line);
13820
13970
  }
13821
13971
  }
13822
13972
  const exitCode = await closePromise;
@@ -14128,7 +14278,7 @@ function registerDriversCommand(program) {
14128
14278
  }
14129
14279
 
14130
14280
  // src/index.ts
14131
- var VERSION = true ? "0.31.1" : "0.0.0-dev";
14281
+ var VERSION = true ? "0.31.3" : "0.0.0-dev";
14132
14282
  function createProgram() {
14133
14283
  const program = new Command();
14134
14284
  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.31.1",
3
+ "version": "0.31.3",
4
4
  "type": "module",
5
5
  "description": "CLI for codeharness — makes autonomous coding agents produce software that actually works",
6
6
  "bin": {