executant 1.10.0 → 1.11.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.
Files changed (2) hide show
  1. package/dist/index.js +216 -134
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -70,7 +70,9 @@ function stripPromptHeader(raw) {
70
70
  return raw.replace(/^(#[^\n]*\n)+\n?/, "").trim();
71
71
  }
72
72
  function loadPrompt(name) {
73
- return stripPromptHeader(readFileSync(join(PROMPTS_DIR, `${name}.txt`), "utf8"));
73
+ return stripPromptHeader(
74
+ readFileSync(join(PROMPTS_DIR, `${name}.txt`), "utf8")
75
+ );
74
76
  }
75
77
  function findOutermostBraces(text) {
76
78
  const start = text.indexOf("{");
@@ -110,6 +112,29 @@ function formatTimestamp(d) {
110
112
  function timestamp() {
111
113
  return formatTimestamp(/* @__PURE__ */ new Date());
112
114
  }
115
+ var ANSI_RE = /\x1B(?:\[[0-9;?]*[A-Za-z]|\][^\x07]*\x07)|[\r]/g;
116
+ function stripAnsi(s) {
117
+ return s.replace(ANSI_RE, "");
118
+ }
119
+ var TOOL_ARG = {
120
+ Read: (i) => String(i["file_path"] ?? i["path"] ?? ""),
121
+ Edit: (i) => String(i["file_path"] ?? ""),
122
+ Write: (i) => String(i["file_path"] ?? ""),
123
+ Bash: (i) => String(i["command"] ?? ""),
124
+ Glob: (i) => String(i["pattern"] ?? ""),
125
+ Grep: (i) => String(i["pattern"] ?? "")
126
+ };
127
+ function getToolArg(tool, input) {
128
+ const fn = TOOL_ARG[tool];
129
+ return fn ? fn(input) : JSON.stringify(input);
130
+ }
131
+ function formatToolCall(tool, input) {
132
+ const fn = TOOL_ARG[tool];
133
+ return fn ? `${tool}(${fn(input)})` : JSON.stringify({ tool, ...input });
134
+ }
135
+ function normalizeError(err) {
136
+ return err instanceof Error ? err : new Error(String(err));
137
+ }
113
138
 
114
139
  // src/load-workflow.ts
115
140
  import { z } from "zod";
@@ -487,10 +512,6 @@ function buildExitError(code, plainLines) {
487
512
  ${plainLines.join("\n")}` : "";
488
513
  return new Error(`claude exited with code ${code}${detail}`);
489
514
  }
490
- var ANSI_RE = /\x1B\[[0-9;]*[A-Za-z]|\x1B\][^\x07]*\x07|\r/g;
491
- function stripAnsi(s) {
492
- return s.replace(ANSI_RE, "");
493
- }
494
515
  function isObject(v) {
495
516
  return typeof v === "object" && v !== null && !Array.isArray(v);
496
517
  }
@@ -539,7 +560,7 @@ function shouldSkipStep(stepNumber, name, options2) {
539
560
  const matchByIndex = /^\d+$/.test(options2.stepFilter) && parseInt(options2.stepFilter, 10) === stepNumber;
540
561
  return !matchByIndex && name !== options2.stepFilter;
541
562
  }
542
- return options2.fromStep !== void 0 && stepNumber < options2.fromStep;
563
+ return options2.fromStep !== void 0 && stepNumber < options2.fromStep[0];
543
564
  }
544
565
  async function* runWorkflow(workflow2, options2 = {}) {
545
566
  const workflowStart = Date.now();
@@ -552,8 +573,9 @@ async function* runWorkflow(workflow2, options2 = {}) {
552
573
  }
553
574
  const stepStart = Date.now();
554
575
  yield { type: "step:start", index: i, name: task.name };
576
+ const from = options2.fromStep && options2.fromStep[0] === stepNumber ? options2.fromStep.slice(1) : void 0;
555
577
  try {
556
- for await (const event of runStep(task)) {
578
+ for await (const event of runStep(task, from)) {
557
579
  if (event.type === "step:iteration" || event.type === "step:inner" || event.type === "output:text" || event.type === "output:tool") {
558
580
  yield { ...event, index: i };
559
581
  } else {
@@ -567,7 +589,7 @@ async function* runWorkflow(workflow2, options2 = {}) {
567
589
  durationMs: Date.now() - stepStart
568
590
  };
569
591
  } catch (err) {
570
- const error = err instanceof Error ? err : new Error(String(err));
592
+ const error = normalizeError(err);
571
593
  yield { type: "step:error", index: i, name: task.name, error };
572
594
  if (!task.continueOnError) throw error;
573
595
  }
@@ -578,7 +600,7 @@ async function* runWorkflow(workflow2, options2 = {}) {
578
600
  durationMs: Date.now() - workflowStart
579
601
  };
580
602
  }
581
- async function* runStep(task) {
603
+ async function* runStep(task, from) {
582
604
  switch (task.type) {
583
605
  case "log":
584
606
  yield* runLog(task);
@@ -601,7 +623,7 @@ async function* runStep(task) {
601
623
  break;
602
624
  }
603
625
  case "forEach":
604
- yield* runForEach(task);
626
+ yield* runForEach(task, from);
605
627
  break;
606
628
  default: {
607
629
  const _ = task;
@@ -612,32 +634,40 @@ async function* runStep(task) {
612
634
  async function* runLog(task) {
613
635
  yield { type: "output:text", index: -1, text: task.message };
614
636
  }
615
- async function* runForEach(task) {
637
+ async function* runForEach(task, from) {
616
638
  const items = await resolveItems(task.forEach);
617
639
  const total = items.length;
618
640
  const innerTotal = task.inner.length;
641
+ const startIteration = from?.[0] ?? 1;
619
642
  for (const [i, item] of items.entries()) {
620
- yield { type: "step:iteration", index: -1, item, iteration: i + 1, total };
643
+ const iteration = i + 1;
644
+ if (iteration < startIteration) continue;
645
+ yield { type: "step:iteration", index: -1, item, iteration, total };
646
+ const iterFrom = iteration === startIteration ? from?.slice(1) : void 0;
647
+ const startChild = iterFrom?.[0] ?? 1;
621
648
  for (const [j, innerTask] of task.inner.entries()) {
649
+ const childIdx = j + 1;
650
+ if (childIdx < startChild) continue;
622
651
  const substituted = substituteItem(innerTask, item);
623
652
  if (innerTotal > 1) {
624
653
  yield {
625
654
  type: "step:inner",
626
655
  index: -1,
627
- iteration: i + 1,
656
+ iteration,
628
657
  innerIndex: j,
629
658
  innerTotal,
630
659
  name: substituted.name
631
660
  };
632
661
  }
662
+ const childFrom = childIdx === startChild ? iterFrom?.slice(1) : void 0;
633
663
  try {
634
- for await (const event of runStep(substituted)) {
664
+ for await (const event of runStep(substituted, childFrom)) {
635
665
  if (event.type !== "step:iteration" && event.type !== "step:inner") {
636
666
  yield event;
637
667
  }
638
668
  }
639
669
  } catch (err) {
640
- const error = err instanceof Error ? err : new Error(String(err));
670
+ const error = normalizeError(err);
641
671
  if (!substituted.continueOnError) {
642
672
  yield {
643
673
  type: "log",
@@ -860,12 +890,6 @@ function buildJudgePrompt(stepName, instructions, output) {
860
890
  OUTPUT: output
861
891
  });
862
892
  }
863
- function formatToolCall(tool, input) {
864
- if (tool === "Edit" || tool === "Write")
865
- return `${tool}(${String(input["file_path"] ?? "")})`;
866
- if (tool === "Bash") return `Bash(${String(input["command"] ?? "")})`;
867
- return tool;
868
- }
869
893
  function buildFixSummary(toolCalls, claudeLines) {
870
894
  if (toolCalls.length > 0) return toolCalls.join(", ");
871
895
  return claudeLines.join(" ").trim() || "No changes made";
@@ -887,7 +911,7 @@ init_update();
887
911
 
888
912
  // src/ui/App.tsx
889
913
  import { useEffect as useEffect2, useReducer, useState } from "react";
890
- import { Box as Box5, Text as Text5, useApp, useStdin } from "ink";
914
+ import { Box as Box5, Text as Text5, useApp, useStdin, useStdout } from "ink";
891
915
 
892
916
  // src/ui/KeyboardHandler.tsx
893
917
  import { useInput } from "ink";
@@ -956,10 +980,10 @@ function reducer(state, event) {
956
980
  startTime: Date.now()
957
981
  });
958
982
  case "step:complete": {
959
- const prev = state.tasks[event.index]?.iterationHistory;
960
- const iterationHistory = prev?.length ? prev.map(
961
- (r) => r.status === "running" ? { ...r, status: "complete", endTime: Date.now() } : r
962
- ) : void 0;
983
+ const iterationHistory = finalizeIterations(
984
+ state.tasks[event.index]?.iterationHistory,
985
+ "complete"
986
+ );
963
987
  return {
964
988
  ...updateTask(state, event.index, {
965
989
  status: "complete",
@@ -970,10 +994,10 @@ function reducer(state, event) {
970
994
  };
971
995
  }
972
996
  case "step:error": {
973
- const prev = state.tasks[event.index]?.iterationHistory;
974
- const iterationHistory = prev?.length ? prev.map(
975
- (r) => r.status === "running" ? { ...r, status: "error", endTime: Date.now() } : r
976
- ) : void 0;
997
+ const iterationHistory = finalizeIterations(
998
+ state.tasks[event.index]?.iterationHistory,
999
+ "error"
1000
+ );
977
1001
  return {
978
1002
  ...updateTask(state, event.index, {
979
1003
  status: "error",
@@ -990,9 +1014,10 @@ function reducer(state, event) {
990
1014
  currentIndex: event.index + 1
991
1015
  };
992
1016
  case "step:iteration": {
993
- const prev = (state.tasks[event.index]?.iterationHistory ?? []).map(
994
- (r) => r.status === "running" ? { ...r, status: "complete", endTime: Date.now() } : r
995
- );
1017
+ const prev = finalizeIterations(
1018
+ state.tasks[event.index]?.iterationHistory,
1019
+ "complete"
1020
+ ) ?? [];
996
1021
  return updateTask(state, event.index, {
997
1022
  iterationHistory: [
998
1023
  ...prev,
@@ -1022,13 +1047,13 @@ function reducer(state, event) {
1022
1047
  case "output:text": {
1023
1048
  const idx = event.index;
1024
1049
  if (idx >= state.tasks.length) return state;
1025
- return appendLine(state, idx, event.text);
1050
+ return appendLines(state, idx, event.text);
1026
1051
  }
1027
1052
  case "output:tool": {
1028
1053
  const idx = event.index;
1029
1054
  if (idx >= state.tasks.length) return state;
1030
1055
  const formatted = formatToolCall2(event.tool, event.input);
1031
- const next = formatted ? appendLine(state, idx, formatted) : state;
1056
+ const next = formatted ? appendLines(state, idx, formatted) : state;
1032
1057
  if (event.tool === "Write" && typeof event.input["file_path"] === "string") {
1033
1058
  return {
1034
1059
  ...next,
@@ -1046,7 +1071,7 @@ function reducer(state, event) {
1046
1071
  case "log": {
1047
1072
  const idx = state.currentIndex;
1048
1073
  if (idx >= state.tasks.length) return state;
1049
- return appendLine(state, idx, `[${event.level}] ${event.text}`);
1074
+ return appendLines(state, idx, `[${event.level}] ${event.text}`);
1050
1075
  }
1051
1076
  default: {
1052
1077
  const _ = event;
@@ -1055,16 +1080,28 @@ function reducer(state, event) {
1055
1080
  }
1056
1081
  }
1057
1082
  }
1083
+ var MAX_LOG_LINES = 300;
1084
+ function normalizeLines(text) {
1085
+ return stripAnsi(text).split("\n");
1086
+ }
1087
+ function finalizeIterations(prev, status) {
1088
+ if (!prev?.length) return void 0;
1089
+ return prev.map(
1090
+ (r) => r.status === "running" ? { ...r, status, endTime: Date.now() } : r
1091
+ );
1092
+ }
1058
1093
  function updateTask(state, index, patch) {
1059
1094
  const tasks = state.tasks.map(
1060
1095
  (t, i) => i === index ? { ...t, ...patch } : t
1061
1096
  );
1062
1097
  return { ...state, tasks };
1063
1098
  }
1064
- function appendLine(state, index, line) {
1099
+ function appendLines(state, index, text) {
1100
+ const newLines = normalizeLines(text);
1065
1101
  const tasks = state.tasks.map((t, i) => {
1066
1102
  if (i !== index) return t;
1067
- const lines = [...t.lines, line];
1103
+ const combined = [...t.lines, ...newLines];
1104
+ const lines = combined.length > MAX_LOG_LINES ? combined.slice(-MAX_LOG_LINES) : combined;
1068
1105
  return { ...t, lines };
1069
1106
  });
1070
1107
  return { ...state, tasks };
@@ -1114,6 +1151,9 @@ var STATUS_COLOR = {
1114
1151
  error: theme.error,
1115
1152
  pending: theme.muted
1116
1153
  };
1154
+ function statusIcon(status, tick) {
1155
+ return status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[status] ?? "\xB7";
1156
+ }
1117
1157
  var EXIT_DELAY_MS = 300;
1118
1158
  function formatHeaderElapsed(start, end) {
1119
1159
  const ms = (end ?? Date.now()) - start;
@@ -1148,9 +1188,6 @@ function TaskRow({ taskState, isActive, index, tick }) {
1148
1188
  ] })
1149
1189
  ] });
1150
1190
  }
1151
- function statusIcon(status, tick) {
1152
- return status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[status] ?? "\xB7";
1153
- }
1154
1191
  function statusColor(status, isActive) {
1155
1192
  if (isActive && status === "running") return theme.primary;
1156
1193
  return STATUS_COLOR[status] ?? theme.foreground;
@@ -1167,7 +1204,7 @@ function formatIterCount(history) {
1167
1204
  import { Box as Box2, Text as Text2 } from "ink";
1168
1205
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1169
1206
  function IterationRow({ record, tick }) {
1170
- const icon = record.status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[record.status] ?? "\xB7";
1207
+ const icon = statusIcon(record.status, tick);
1171
1208
  const color = STATUS_COLOR[record.status] ?? theme.primary;
1172
1209
  const innerText = record.inner ? ` \u2014 ${stripItem(record.inner.name, record.item)} [${record.inner.index + 1}/${record.inner.total}]` : "";
1173
1210
  const ms = (record.endTime ?? Date.now()) - record.startTime;
@@ -1222,14 +1259,24 @@ function LogPane({ lines, isActive = false, maxLines = 15 }) {
1222
1259
  if (visible.length === 0) {
1223
1260
  return /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isActive ? "\u2838 waiting for output\u2026" : "\u2014 no output yet \u2014" }) });
1224
1261
  }
1225
- return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: theme.border, paddingX: 1, children: visible.map((line, i) => /* @__PURE__ */ jsx3(
1226
- LogLine,
1262
+ return /* @__PURE__ */ jsx3(
1263
+ Box3,
1227
1264
  {
1228
- text: line,
1229
- cursor: isActive && i === visible.length - 1
1230
- },
1231
- i
1232
- )) });
1265
+ flexDirection: "column",
1266
+ marginTop: 1,
1267
+ borderStyle: "single",
1268
+ borderColor: theme.border,
1269
+ paddingX: 1,
1270
+ children: visible.map((line, i) => /* @__PURE__ */ jsx3(
1271
+ LogLine,
1272
+ {
1273
+ text: line,
1274
+ cursor: isActive && i === visible.length - 1
1275
+ },
1276
+ i
1277
+ ))
1278
+ }
1279
+ );
1233
1280
  }
1234
1281
  function LogLine({ text, cursor }) {
1235
1282
  const suffix = cursor ? /* @__PURE__ */ jsx3(Text3, { color: theme.primary, children: " \u258C" }) : null;
@@ -1242,18 +1289,41 @@ function LogLine({ text, cursor }) {
1242
1289
  suffix
1243
1290
  ] });
1244
1291
  }
1245
- if (/^\s*\$\s/.test(text)) return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1246
- text,
1247
- suffix
1248
- ] });
1249
- if (text.startsWith("[warn]")) return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1250
- text,
1251
- suffix
1252
- ] });
1253
- if (text.startsWith("[error]")) return /* @__PURE__ */ jsxs3(Text3, { color: theme.error, children: [
1254
- text,
1255
- suffix
1256
- ] });
1292
+ if (/^\s*\$\s/.test(text))
1293
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1294
+ text,
1295
+ suffix
1296
+ ] });
1297
+ if (text.startsWith("[warn]"))
1298
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1299
+ text,
1300
+ suffix
1301
+ ] });
1302
+ if (text.startsWith("[error]"))
1303
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.error, children: [
1304
+ text,
1305
+ suffix
1306
+ ] });
1307
+ if (/^[\s]*(✓|✔|✅|done|success|compiled|built|passed)/i.test(text) && !/\b(error|fail|failed|warn|warning)\b/i.test(text))
1308
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.success, children: [
1309
+ text,
1310
+ suffix
1311
+ ] });
1312
+ if (/\b(error|failed|fail)\b/i.test(text))
1313
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.error, children: [
1314
+ text,
1315
+ suffix
1316
+ ] });
1317
+ if (/\b(warn|warning)\b/i.test(text))
1318
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1319
+ text,
1320
+ suffix
1321
+ ] });
1322
+ if (/^[·…⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/.test(text))
1323
+ return /* @__PURE__ */ jsxs3(Text3, { color: theme.muted, children: [
1324
+ text,
1325
+ suffix
1326
+ ] });
1257
1327
  return /* @__PURE__ */ jsxs3(Text3, { children: [
1258
1328
  text,
1259
1329
  suffix
@@ -1324,6 +1394,13 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1324
1394
  };
1325
1395
  }, [events2, exit]);
1326
1396
  const { isRawModeSupported } = useStdin();
1397
+ const { stdout } = useStdout();
1398
+ const terminalRows = stdout?.rows ?? 24;
1399
+ const FIXED_OVERHEAD = 12;
1400
+ const logPaneMaxLines = Math.max(
1401
+ 5,
1402
+ terminalRows - FIXED_OVERHEAD - state.tasks.length
1403
+ );
1327
1404
  const [tick, setTick] = useState(0);
1328
1405
  useInterval(() => {
1329
1406
  if (!state.endTime) setTick((t) => t + 1);
@@ -1376,7 +1453,8 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1376
1453
  LogPane,
1377
1454
  {
1378
1455
  lines: activeTask.lines,
1379
- isActive: activeTask.status === "running"
1456
+ isActive: activeTask.status === "running",
1457
+ maxLines: logPaneMaxLines
1380
1458
  }
1381
1459
  ),
1382
1460
  state.endTime !== void 0 && state.writtenFiles.length > 0 && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, children: [
@@ -2005,17 +2083,6 @@ var INIT_STATE = {
2005
2083
  judgeAttempt: 0,
2006
2084
  recentOutput: []
2007
2085
  };
2008
- var TOOL_SUMMARY = {
2009
- Read: (i) => String(i["file_path"] ?? i["path"] ?? ""),
2010
- Edit: (i) => String(i["file_path"] ?? ""),
2011
- Write: (i) => String(i["file_path"] ?? ""),
2012
- Bash: (i) => String(i["command"] ?? ""),
2013
- Glob: (i) => String(i["pattern"] ?? ""),
2014
- Grep: (i) => String(i["pattern"] ?? "")
2015
- };
2016
- function toolSummary(tool, input) {
2017
- return (TOOL_SUMMARY[tool] ?? ((i) => JSON.stringify(i)))(input);
2018
- }
2019
2086
  function appendLog(logFile, text) {
2020
2087
  if (logFile) appendFileSync(logFile, text + "\n");
2021
2088
  }
@@ -2087,21 +2154,21 @@ Step failed: ${error.message}
2087
2154
  finalizeComplexSequence(s);
2088
2155
  return s;
2089
2156
  }
2090
- function complexSequenceHeader(ctx, s) {
2157
+ function buildHighlightHeader(ctx, s, title, extra = []) {
2091
2158
  return [
2092
- "# Complex Tool Sequence",
2159
+ `# ${title}`,
2093
2160
  "",
2094
2161
  `**Task:** ${ctx.slug}`,
2095
2162
  `**Step:** ${s.stepName}`,
2163
+ ...extra,
2096
2164
  `**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
2097
2165
  "",
2098
2166
  "---",
2099
- "",
2100
- "## Claude's Tool Orchestration",
2101
- "",
2102
- "Claude used multiple tools to complete this step:",
2103
2167
  ""
2104
- ].join("\n");
2168
+ ].join("\n") + "\n";
2169
+ }
2170
+ function complexSequenceHeader(ctx, s) {
2171
+ return buildHighlightHeader(ctx, s, "Complex Tool Sequence") + "## Claude's Tool Orchestration\n\nClaude used multiple tools to complete this step:\n\n";
2105
2172
  }
2106
2173
  function createComplexSequenceFile(ctx, s) {
2107
2174
  const path = highlightPath(ctx, s.stepIndex, "complex_sequence");
@@ -2109,7 +2176,7 @@ function createComplexSequenceFile(ctx, s) {
2109
2176
  return path;
2110
2177
  }
2111
2178
  function onTool(ctx, s, tool, input) {
2112
- const desc = toolSummary(tool, input);
2179
+ const desc = getToolArg(tool, input);
2113
2180
  appendLog(s.logFile, ` [${tool}] ${desc}`);
2114
2181
  const toolCount = s.toolCount + 1;
2115
2182
  const complexSequenceFile = toolCount === 3 ? createComplexSequenceFile(ctx, s) : s.complexSequenceFile;
@@ -2125,23 +2192,9 @@ function onTool(ctx, s, tool, input) {
2125
2192
  function saveJudgeHighlight(ctx, s, verdict, text) {
2126
2193
  writeFileSync3(
2127
2194
  highlightPath(ctx, s.stepIndex, `judge_${verdict}`),
2128
- [
2129
- `# Judge Verdict: ${verdict}`,
2130
- "",
2131
- `**Task:** ${ctx.slug}`,
2132
- `**Step:** ${s.stepName}`,
2133
- `**Attempt:** ${s.judgeAttempt}`,
2134
- `**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
2135
- "",
2136
- "---",
2137
- "",
2138
- text,
2139
- "",
2140
- "---",
2141
- "",
2142
- "*Auto-captured*",
2143
- ""
2144
- ].join("\n")
2195
+ buildHighlightHeader(ctx, s, `Judge Verdict: ${verdict}`, [
2196
+ `**Attempt:** ${s.judgeAttempt}`
2197
+ ]) + [text, "", "---", "", "*Auto-captured*", ""].join("\n")
2145
2198
  );
2146
2199
  }
2147
2200
  var LOG_MATCHERS = [
@@ -2156,19 +2209,11 @@ var LOG_MATCHERS = [
2156
2209
  },
2157
2210
  {
2158
2211
  pattern: /\[self-healing\].*failed.*exit\s+(\d+)/i,
2159
- apply: (ctx, s, text, match) => {
2212
+ apply: (ctx, s, _text, match) => {
2160
2213
  const selfHealingFile = highlightPath(ctx, s.stepIndex, "self_healing");
2161
2214
  writeFileSync3(
2162
2215
  selfHealingFile,
2163
- [
2164
- "# Self-Healing Activation",
2165
- "",
2166
- `**Task:** ${ctx.slug}`,
2167
- `**Step:** ${s.stepName}`,
2168
- `**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
2169
- "",
2170
- "---",
2171
- "",
2216
+ buildHighlightHeader(ctx, s, "Self-Healing Activation") + [
2172
2217
  "## \u274C Failure Detected",
2173
2218
  "",
2174
2219
  `**Exit Code:** ${match[1]}`,
@@ -2215,14 +2260,15 @@ var LOG_MATCHERS = [
2215
2260
  ];
2216
2261
  function onLogMessage(ctx, s, level, text) {
2217
2262
  appendLog(s.logFile, `[${level}] ${text}`);
2218
- return LOG_MATCHERS.reduce(
2219
- ({ matched, state }, { pattern, apply }) => {
2220
- if (matched) return { matched, state };
2221
- const m = pattern.exec(text);
2222
- return m ? { matched: true, state: apply(ctx, state, text, m) } : { matched, state };
2223
- },
2224
- { matched: false, state: s }
2225
- ).state;
2263
+ let state = s;
2264
+ for (const { pattern, apply } of LOG_MATCHERS) {
2265
+ const m = pattern.exec(text);
2266
+ if (m) {
2267
+ state = apply(ctx, state, text, m);
2268
+ break;
2269
+ }
2270
+ }
2271
+ return state;
2226
2272
  }
2227
2273
  function onWorkflowComplete(ctx, s) {
2228
2274
  appendLog(
@@ -2330,7 +2376,13 @@ async function* withLogger(gen, logger2) {
2330
2376
  }
2331
2377
 
2332
2378
  // src/retrospective.ts
2333
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
2379
+ import {
2380
+ existsSync as existsSync3,
2381
+ mkdirSync as mkdirSync4,
2382
+ readdirSync as readdirSync2,
2383
+ readFileSync as readFileSync5,
2384
+ writeFileSync as writeFileSync4
2385
+ } from "node:fs";
2334
2386
  import { basename as basename2, dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
2335
2387
  import { spawnSync } from "node:child_process";
2336
2388
  import { load as parseYaml2 } from "js-yaml";
@@ -2342,10 +2394,17 @@ var RetrospectiveOutputSchema = z4.object({
2342
2394
  var RETROSPECTIVE_PROMPT = loadPrompt("retrospective-analysis");
2343
2395
  async function runRetrospective(workflowFilePath, workflow2, highlightsDir, runTimestamp) {
2344
2396
  try {
2345
- await doRetrospective(workflowFilePath, workflow2, highlightsDir, runTimestamp);
2397
+ await doRetrospective(
2398
+ workflowFilePath,
2399
+ workflow2,
2400
+ highlightsDir,
2401
+ runTimestamp
2402
+ );
2346
2403
  } catch (err) {
2347
- console.warn(`
2348
- Self-improvement: retrospective failed: ${getErrorMessage(err)}`);
2404
+ console.warn(
2405
+ `
2406
+ Self-improvement: retrospective failed: ${getErrorMessage(err)}`
2407
+ );
2349
2408
  }
2350
2409
  }
2351
2410
  async function doRetrospective(workflowFilePath, workflow2, highlightsDir, runTimestamp) {
@@ -2356,13 +2415,17 @@ async function doRetrospective(workflowFilePath, workflow2, highlightsDir, runTi
2356
2415
  const allFiles = readdirSync2(highlightsDir);
2357
2416
  const runHighlights = allFiles.filter((f) => f.startsWith(runTimestamp) && f.endsWith(".md")).sort();
2358
2417
  if (runHighlights.length === 0) {
2359
- console.log("\nSelf-improvement: no highlights for this run \u2014 task completed without issues, skipping.");
2418
+ console.log(
2419
+ "\nSelf-improvement: no highlights for this run \u2014 task completed without issues, skipping."
2420
+ );
2360
2421
  return;
2361
2422
  }
2362
2423
  const divider = "\u2501".repeat(51);
2363
2424
  console.log(`
2364
2425
  ${divider}`);
2365
- console.log("Self-Improvement: Analyzing execution and generating improvements...");
2426
+ console.log(
2427
+ "Self-Improvement: Analyzing execution and generating improvements..."
2428
+ );
2366
2429
  console.log(`${divider}
2367
2430
  `);
2368
2431
  console.log(`Found ${runHighlights.length} highlight(s) to analyze`);
@@ -2408,15 +2471,23 @@ ${content}`;
2408
2471
  "--output-format",
2409
2472
  "text"
2410
2473
  ],
2411
- { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 }
2474
+ {
2475
+ encoding: "utf8",
2476
+ maxBuffer: 10 * 1024 * 1024,
2477
+ stdio: ["ignore", "pipe", "pipe"]
2478
+ }
2412
2479
  );
2413
2480
  if (result.error) {
2414
- console.warn(`Self-improvement: failed to run claude: ${result.error.message}`);
2481
+ console.warn(
2482
+ `Self-improvement: failed to run claude: ${result.error.message}`
2483
+ );
2415
2484
  return;
2416
2485
  }
2417
2486
  if (result.status !== 0) {
2418
2487
  const stderr = result.stderr ?? "";
2419
- console.warn(`Self-improvement: claude exited with code ${result.status}${stderr ? ": " + stderr : ""}`);
2488
+ console.warn(
2489
+ `Self-improvement: claude exited with code ${result.status}${stderr ? ": " + stderr : ""}`
2490
+ );
2420
2491
  return;
2421
2492
  }
2422
2493
  const response = result.stdout ?? "";
@@ -2424,13 +2495,17 @@ ${content}`;
2424
2495
  try {
2425
2496
  parsed = JSON.parse(extractJson(response));
2426
2497
  } catch {
2427
- console.warn(`Self-improvement: could not parse Claude response as JSON.
2428
- Response: ${response.trim()}`);
2498
+ console.warn(
2499
+ `Self-improvement: could not parse Claude response as JSON.
2500
+ Response: ${response.trim()}`
2501
+ );
2429
2502
  return;
2430
2503
  }
2431
2504
  const zodResult = RetrospectiveOutputSchema.safeParse(parsed);
2432
2505
  if (!zodResult.success) {
2433
- console.warn("Self-improvement: response schema mismatch \u2014 improved YAML not saved.");
2506
+ console.warn(
2507
+ "Self-improvement: response schema mismatch \u2014 improved YAML not saved."
2508
+ );
2434
2509
  return;
2435
2510
  }
2436
2511
  const improvedYaml = zodResult.data.improved_yaml.trim();
@@ -2438,7 +2513,9 @@ Response: ${response.trim()}`);
2438
2513
  try {
2439
2514
  parseYaml2(improvedYaml);
2440
2515
  } catch (err) {
2441
- console.warn(`Self-improvement: generated YAML is invalid (${getErrorMessage(err)}), skipping save.`);
2516
+ console.warn(
2517
+ `Self-improvement: generated YAML is invalid (${getErrorMessage(err)}), skipping save.`
2518
+ );
2442
2519
  return;
2443
2520
  }
2444
2521
  const startDir = dirname4(resolve3(workflowFilePath));
@@ -2463,7 +2540,8 @@ ${divider}`);
2463
2540
  function extractJson(text) {
2464
2541
  const start = text.indexOf("{");
2465
2542
  const end = text.lastIndexOf("}");
2466
- if (start === -1 || end === -1 || end <= start) throw new Error("no JSON object found in response");
2543
+ if (start === -1 || end === -1 || end <= start)
2544
+ throw new Error("no JSON object found in response");
2467
2545
  return text.slice(start, end + 1);
2468
2546
  }
2469
2547
 
@@ -2513,7 +2591,7 @@ Version: ${CURRENT_VERSION}
2513
2591
  Options:
2514
2592
  --ci Headless mode \u2014 print events as NDJSON, no TUI
2515
2593
  --step <name|index> Run only the named step or step at 1-based index
2516
- --from-step <n> Skip steps before n and run from there (1-based)
2594
+ --from-step <n> Resume from step n (e.g. 3, 3.2, 2.5.4.3 \u2014 1-based path)
2517
2595
  --help, -h Show this help
2518
2596
 
2519
2597
  Commands:
@@ -2595,11 +2673,15 @@ for (let i = 0; i < rawArgs.length; i++) {
2595
2673
  console.error("--from-step requires a value");
2596
2674
  process.exit(1);
2597
2675
  }
2598
- fromStep = parseInt(rawArgs[++i], 10);
2599
- if (isNaN(fromStep)) {
2600
- console.error("--from-step must be a number");
2676
+ const raw = rawArgs[++i];
2677
+ const parts = raw.split(".").map(Number);
2678
+ if (parts.some(Number.isNaN) || parts.some((p) => p < 1)) {
2679
+ console.error(
2680
+ "--from-step must be N or N.M.K... (all 1-based, e.g. 3 or 3.2 or 2.5.4.3)"
2681
+ );
2601
2682
  process.exit(1);
2602
2683
  }
2684
+ fromStep = parts;
2603
2685
  } else {
2604
2686
  positional.push(a);
2605
2687
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executant",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Harness for YAML-defined workflows that enables stepping through Claude sessions and bash commands",
5
5
  "repository": {
6
6
  "type": "git",