executant 1.10.1 → 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.
- package/dist/index.js +99 -100
- 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(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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";
|
|
@@ -956,10 +980,10 @@ function reducer(state, event) {
|
|
|
956
980
|
startTime: Date.now()
|
|
957
981
|
});
|
|
958
982
|
case "step:complete": {
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
)
|
|
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
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
)
|
|
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 = (
|
|
994
|
-
|
|
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,
|
|
@@ -1028,7 +1053,7 @@ function reducer(state, event) {
|
|
|
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 ?
|
|
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,
|
|
@@ -1055,10 +1080,15 @@ function reducer(state, event) {
|
|
|
1055
1080
|
}
|
|
1056
1081
|
}
|
|
1057
1082
|
}
|
|
1058
|
-
var ANSI_RE2 = /\x1B(?:\[[0-9;?]*[A-Za-z]|\][^\x07]*\x07)|[\r]/g;
|
|
1059
1083
|
var MAX_LOG_LINES = 300;
|
|
1060
1084
|
function normalizeLines(text) {
|
|
1061
|
-
return 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
|
+
);
|
|
1062
1092
|
}
|
|
1063
1093
|
function updateTask(state, index, patch) {
|
|
1064
1094
|
const tasks = state.tasks.map(
|
|
@@ -1066,9 +1096,6 @@ function updateTask(state, index, patch) {
|
|
|
1066
1096
|
);
|
|
1067
1097
|
return { ...state, tasks };
|
|
1068
1098
|
}
|
|
1069
|
-
function appendLine(state, index, line) {
|
|
1070
|
-
return appendLines(state, index, line);
|
|
1071
|
-
}
|
|
1072
1099
|
function appendLines(state, index, text) {
|
|
1073
1100
|
const newLines = normalizeLines(text);
|
|
1074
1101
|
const tasks = state.tasks.map((t, i) => {
|
|
@@ -1124,6 +1151,9 @@ var STATUS_COLOR = {
|
|
|
1124
1151
|
error: theme.error,
|
|
1125
1152
|
pending: theme.muted
|
|
1126
1153
|
};
|
|
1154
|
+
function statusIcon(status, tick) {
|
|
1155
|
+
return status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[status] ?? "\xB7";
|
|
1156
|
+
}
|
|
1127
1157
|
var EXIT_DELAY_MS = 300;
|
|
1128
1158
|
function formatHeaderElapsed(start, end) {
|
|
1129
1159
|
const ms = (end ?? Date.now()) - start;
|
|
@@ -1158,9 +1188,6 @@ function TaskRow({ taskState, isActive, index, tick }) {
|
|
|
1158
1188
|
] })
|
|
1159
1189
|
] });
|
|
1160
1190
|
}
|
|
1161
|
-
function statusIcon(status, tick) {
|
|
1162
|
-
return status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[status] ?? "\xB7";
|
|
1163
|
-
}
|
|
1164
1191
|
function statusColor(status, isActive) {
|
|
1165
1192
|
if (isActive && status === "running") return theme.primary;
|
|
1166
1193
|
return STATUS_COLOR[status] ?? theme.foreground;
|
|
@@ -1177,7 +1204,7 @@ function formatIterCount(history) {
|
|
|
1177
1204
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
1178
1205
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1179
1206
|
function IterationRow({ record, tick }) {
|
|
1180
|
-
const icon = record.status
|
|
1207
|
+
const icon = statusIcon(record.status, tick);
|
|
1181
1208
|
const color = STATUS_COLOR[record.status] ?? theme.primary;
|
|
1182
1209
|
const innerText = record.inner ? ` \u2014 ${stripItem(record.inner.name, record.item)} [${record.inner.index + 1}/${record.inner.total}]` : "";
|
|
1183
1210
|
const ms = (record.endTime ?? Date.now()) - record.startTime;
|
|
@@ -2056,17 +2083,6 @@ var INIT_STATE = {
|
|
|
2056
2083
|
judgeAttempt: 0,
|
|
2057
2084
|
recentOutput: []
|
|
2058
2085
|
};
|
|
2059
|
-
var TOOL_SUMMARY = {
|
|
2060
|
-
Read: (i) => String(i["file_path"] ?? i["path"] ?? ""),
|
|
2061
|
-
Edit: (i) => String(i["file_path"] ?? ""),
|
|
2062
|
-
Write: (i) => String(i["file_path"] ?? ""),
|
|
2063
|
-
Bash: (i) => String(i["command"] ?? ""),
|
|
2064
|
-
Glob: (i) => String(i["pattern"] ?? ""),
|
|
2065
|
-
Grep: (i) => String(i["pattern"] ?? "")
|
|
2066
|
-
};
|
|
2067
|
-
function toolSummary(tool, input) {
|
|
2068
|
-
return (TOOL_SUMMARY[tool] ?? ((i) => JSON.stringify(i)))(input);
|
|
2069
|
-
}
|
|
2070
2086
|
function appendLog(logFile, text) {
|
|
2071
2087
|
if (logFile) appendFileSync(logFile, text + "\n");
|
|
2072
2088
|
}
|
|
@@ -2138,21 +2154,21 @@ Step failed: ${error.message}
|
|
|
2138
2154
|
finalizeComplexSequence(s);
|
|
2139
2155
|
return s;
|
|
2140
2156
|
}
|
|
2141
|
-
function
|
|
2157
|
+
function buildHighlightHeader(ctx, s, title, extra = []) {
|
|
2142
2158
|
return [
|
|
2143
|
-
|
|
2159
|
+
`# ${title}`,
|
|
2144
2160
|
"",
|
|
2145
2161
|
`**Task:** ${ctx.slug}`,
|
|
2146
2162
|
`**Step:** ${s.stepName}`,
|
|
2163
|
+
...extra,
|
|
2147
2164
|
`**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2148
2165
|
"",
|
|
2149
2166
|
"---",
|
|
2150
|
-
"",
|
|
2151
|
-
"## Claude's Tool Orchestration",
|
|
2152
|
-
"",
|
|
2153
|
-
"Claude used multiple tools to complete this step:",
|
|
2154
2167
|
""
|
|
2155
|
-
].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";
|
|
2156
2172
|
}
|
|
2157
2173
|
function createComplexSequenceFile(ctx, s) {
|
|
2158
2174
|
const path = highlightPath(ctx, s.stepIndex, "complex_sequence");
|
|
@@ -2160,7 +2176,7 @@ function createComplexSequenceFile(ctx, s) {
|
|
|
2160
2176
|
return path;
|
|
2161
2177
|
}
|
|
2162
2178
|
function onTool(ctx, s, tool, input) {
|
|
2163
|
-
const desc =
|
|
2179
|
+
const desc = getToolArg(tool, input);
|
|
2164
2180
|
appendLog(s.logFile, ` [${tool}] ${desc}`);
|
|
2165
2181
|
const toolCount = s.toolCount + 1;
|
|
2166
2182
|
const complexSequenceFile = toolCount === 3 ? createComplexSequenceFile(ctx, s) : s.complexSequenceFile;
|
|
@@ -2176,23 +2192,9 @@ function onTool(ctx, s, tool, input) {
|
|
|
2176
2192
|
function saveJudgeHighlight(ctx, s, verdict, text) {
|
|
2177
2193
|
writeFileSync3(
|
|
2178
2194
|
highlightPath(ctx, s.stepIndex, `judge_${verdict}`),
|
|
2179
|
-
[
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
`**Task:** ${ctx.slug}`,
|
|
2183
|
-
`**Step:** ${s.stepName}`,
|
|
2184
|
-
`**Attempt:** ${s.judgeAttempt}`,
|
|
2185
|
-
`**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2186
|
-
"",
|
|
2187
|
-
"---",
|
|
2188
|
-
"",
|
|
2189
|
-
text,
|
|
2190
|
-
"",
|
|
2191
|
-
"---",
|
|
2192
|
-
"",
|
|
2193
|
-
"*Auto-captured*",
|
|
2194
|
-
""
|
|
2195
|
-
].join("\n")
|
|
2195
|
+
buildHighlightHeader(ctx, s, `Judge Verdict: ${verdict}`, [
|
|
2196
|
+
`**Attempt:** ${s.judgeAttempt}`
|
|
2197
|
+
]) + [text, "", "---", "", "*Auto-captured*", ""].join("\n")
|
|
2196
2198
|
);
|
|
2197
2199
|
}
|
|
2198
2200
|
var LOG_MATCHERS = [
|
|
@@ -2207,19 +2209,11 @@ var LOG_MATCHERS = [
|
|
|
2207
2209
|
},
|
|
2208
2210
|
{
|
|
2209
2211
|
pattern: /\[self-healing\].*failed.*exit\s+(\d+)/i,
|
|
2210
|
-
apply: (ctx, s,
|
|
2212
|
+
apply: (ctx, s, _text, match) => {
|
|
2211
2213
|
const selfHealingFile = highlightPath(ctx, s.stepIndex, "self_healing");
|
|
2212
2214
|
writeFileSync3(
|
|
2213
2215
|
selfHealingFile,
|
|
2214
|
-
[
|
|
2215
|
-
"# Self-Healing Activation",
|
|
2216
|
-
"",
|
|
2217
|
-
`**Task:** ${ctx.slug}`,
|
|
2218
|
-
`**Step:** ${s.stepName}`,
|
|
2219
|
-
`**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2220
|
-
"",
|
|
2221
|
-
"---",
|
|
2222
|
-
"",
|
|
2216
|
+
buildHighlightHeader(ctx, s, "Self-Healing Activation") + [
|
|
2223
2217
|
"## \u274C Failure Detected",
|
|
2224
2218
|
"",
|
|
2225
2219
|
`**Exit Code:** ${match[1]}`,
|
|
@@ -2266,14 +2260,15 @@ var LOG_MATCHERS = [
|
|
|
2266
2260
|
];
|
|
2267
2261
|
function onLogMessage(ctx, s, level, text) {
|
|
2268
2262
|
appendLog(s.logFile, `[${level}] ${text}`);
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
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;
|
|
2277
2272
|
}
|
|
2278
2273
|
function onWorkflowComplete(ctx, s) {
|
|
2279
2274
|
appendLog(
|
|
@@ -2596,7 +2591,7 @@ Version: ${CURRENT_VERSION}
|
|
|
2596
2591
|
Options:
|
|
2597
2592
|
--ci Headless mode \u2014 print events as NDJSON, no TUI
|
|
2598
2593
|
--step <name|index> Run only the named step or step at 1-based index
|
|
2599
|
-
--from-step <n>
|
|
2594
|
+
--from-step <n> Resume from step n (e.g. 3, 3.2, 2.5.4.3 \u2014 1-based path)
|
|
2600
2595
|
--help, -h Show this help
|
|
2601
2596
|
|
|
2602
2597
|
Commands:
|
|
@@ -2678,11 +2673,15 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
2678
2673
|
console.error("--from-step requires a value");
|
|
2679
2674
|
process.exit(1);
|
|
2680
2675
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
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
|
+
);
|
|
2684
2682
|
process.exit(1);
|
|
2685
2683
|
}
|
|
2684
|
+
fromStep = parts;
|
|
2686
2685
|
} else {
|
|
2687
2686
|
positional.push(a);
|
|
2688
2687
|
}
|