executant 1.10.1 → 1.12.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 +107 -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,48 @@ 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;
|
|
642
|
+
if (startIteration > 1 && startIteration > total) {
|
|
643
|
+
yield {
|
|
644
|
+
type: "log",
|
|
645
|
+
level: "warn",
|
|
646
|
+
text: `[from-step] No iterations to run: target iteration ${startIteration} exceeds total ${total} in "${task.name}"`
|
|
647
|
+
};
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
619
650
|
for (const [i, item] of items.entries()) {
|
|
620
|
-
|
|
651
|
+
const iteration = i + 1;
|
|
652
|
+
if (iteration < startIteration) continue;
|
|
653
|
+
yield { type: "step:iteration", index: -1, item, iteration, total };
|
|
654
|
+
const iterFrom = iteration === startIteration ? from?.slice(1) : void 0;
|
|
655
|
+
const startChild = iterFrom?.[0] ?? 1;
|
|
621
656
|
for (const [j, innerTask] of task.inner.entries()) {
|
|
657
|
+
const childIdx = j + 1;
|
|
658
|
+
if (childIdx < startChild) continue;
|
|
622
659
|
const substituted = substituteItem(innerTask, item);
|
|
623
660
|
if (innerTotal > 1) {
|
|
624
661
|
yield {
|
|
625
662
|
type: "step:inner",
|
|
626
663
|
index: -1,
|
|
627
|
-
iteration
|
|
664
|
+
iteration,
|
|
628
665
|
innerIndex: j,
|
|
629
666
|
innerTotal,
|
|
630
667
|
name: substituted.name
|
|
631
668
|
};
|
|
632
669
|
}
|
|
670
|
+
const childFrom = childIdx === startChild ? iterFrom?.slice(1) : void 0;
|
|
633
671
|
try {
|
|
634
|
-
for await (const event of runStep(substituted)) {
|
|
672
|
+
for await (const event of runStep(substituted, childFrom)) {
|
|
635
673
|
if (event.type !== "step:iteration" && event.type !== "step:inner") {
|
|
636
674
|
yield event;
|
|
637
675
|
}
|
|
638
676
|
}
|
|
639
677
|
} catch (err) {
|
|
640
|
-
const error =
|
|
678
|
+
const error = normalizeError(err);
|
|
641
679
|
if (!substituted.continueOnError) {
|
|
642
680
|
yield {
|
|
643
681
|
type: "log",
|
|
@@ -860,12 +898,6 @@ function buildJudgePrompt(stepName, instructions, output) {
|
|
|
860
898
|
OUTPUT: output
|
|
861
899
|
});
|
|
862
900
|
}
|
|
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
901
|
function buildFixSummary(toolCalls, claudeLines) {
|
|
870
902
|
if (toolCalls.length > 0) return toolCalls.join(", ");
|
|
871
903
|
return claudeLines.join(" ").trim() || "No changes made";
|
|
@@ -956,10 +988,10 @@ function reducer(state, event) {
|
|
|
956
988
|
startTime: Date.now()
|
|
957
989
|
});
|
|
958
990
|
case "step:complete": {
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
)
|
|
991
|
+
const iterationHistory = finalizeIterations(
|
|
992
|
+
state.tasks[event.index]?.iterationHistory,
|
|
993
|
+
"complete"
|
|
994
|
+
);
|
|
963
995
|
return {
|
|
964
996
|
...updateTask(state, event.index, {
|
|
965
997
|
status: "complete",
|
|
@@ -970,10 +1002,10 @@ function reducer(state, event) {
|
|
|
970
1002
|
};
|
|
971
1003
|
}
|
|
972
1004
|
case "step:error": {
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
)
|
|
1005
|
+
const iterationHistory = finalizeIterations(
|
|
1006
|
+
state.tasks[event.index]?.iterationHistory,
|
|
1007
|
+
"error"
|
|
1008
|
+
);
|
|
977
1009
|
return {
|
|
978
1010
|
...updateTask(state, event.index, {
|
|
979
1011
|
status: "error",
|
|
@@ -990,9 +1022,10 @@ function reducer(state, event) {
|
|
|
990
1022
|
currentIndex: event.index + 1
|
|
991
1023
|
};
|
|
992
1024
|
case "step:iteration": {
|
|
993
|
-
const prev = (
|
|
994
|
-
|
|
995
|
-
|
|
1025
|
+
const prev = finalizeIterations(
|
|
1026
|
+
state.tasks[event.index]?.iterationHistory,
|
|
1027
|
+
"complete"
|
|
1028
|
+
) ?? [];
|
|
996
1029
|
return updateTask(state, event.index, {
|
|
997
1030
|
iterationHistory: [
|
|
998
1031
|
...prev,
|
|
@@ -1028,7 +1061,7 @@ function reducer(state, event) {
|
|
|
1028
1061
|
const idx = event.index;
|
|
1029
1062
|
if (idx >= state.tasks.length) return state;
|
|
1030
1063
|
const formatted = formatToolCall2(event.tool, event.input);
|
|
1031
|
-
const next = formatted ?
|
|
1064
|
+
const next = formatted ? appendLines(state, idx, formatted) : state;
|
|
1032
1065
|
if (event.tool === "Write" && typeof event.input["file_path"] === "string") {
|
|
1033
1066
|
return {
|
|
1034
1067
|
...next,
|
|
@@ -1055,10 +1088,15 @@ function reducer(state, event) {
|
|
|
1055
1088
|
}
|
|
1056
1089
|
}
|
|
1057
1090
|
}
|
|
1058
|
-
var ANSI_RE2 = /\x1B(?:\[[0-9;?]*[A-Za-z]|\][^\x07]*\x07)|[\r]/g;
|
|
1059
1091
|
var MAX_LOG_LINES = 300;
|
|
1060
1092
|
function normalizeLines(text) {
|
|
1061
|
-
return text
|
|
1093
|
+
return stripAnsi(text).split("\n");
|
|
1094
|
+
}
|
|
1095
|
+
function finalizeIterations(prev, status) {
|
|
1096
|
+
if (!prev?.length) return void 0;
|
|
1097
|
+
return prev.map(
|
|
1098
|
+
(r) => r.status === "running" ? { ...r, status, endTime: Date.now() } : r
|
|
1099
|
+
);
|
|
1062
1100
|
}
|
|
1063
1101
|
function updateTask(state, index, patch) {
|
|
1064
1102
|
const tasks = state.tasks.map(
|
|
@@ -1066,9 +1104,6 @@ function updateTask(state, index, patch) {
|
|
|
1066
1104
|
);
|
|
1067
1105
|
return { ...state, tasks };
|
|
1068
1106
|
}
|
|
1069
|
-
function appendLine(state, index, line) {
|
|
1070
|
-
return appendLines(state, index, line);
|
|
1071
|
-
}
|
|
1072
1107
|
function appendLines(state, index, text) {
|
|
1073
1108
|
const newLines = normalizeLines(text);
|
|
1074
1109
|
const tasks = state.tasks.map((t, i) => {
|
|
@@ -1124,6 +1159,9 @@ var STATUS_COLOR = {
|
|
|
1124
1159
|
error: theme.error,
|
|
1125
1160
|
pending: theme.muted
|
|
1126
1161
|
};
|
|
1162
|
+
function statusIcon(status, tick) {
|
|
1163
|
+
return status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[status] ?? "\xB7";
|
|
1164
|
+
}
|
|
1127
1165
|
var EXIT_DELAY_MS = 300;
|
|
1128
1166
|
function formatHeaderElapsed(start, end) {
|
|
1129
1167
|
const ms = (end ?? Date.now()) - start;
|
|
@@ -1158,9 +1196,6 @@ function TaskRow({ taskState, isActive, index, tick }) {
|
|
|
1158
1196
|
] })
|
|
1159
1197
|
] });
|
|
1160
1198
|
}
|
|
1161
|
-
function statusIcon(status, tick) {
|
|
1162
|
-
return status === "running" ? SPINNER[tick % SPINNER.length] : STATUS_ICON[status] ?? "\xB7";
|
|
1163
|
-
}
|
|
1164
1199
|
function statusColor(status, isActive) {
|
|
1165
1200
|
if (isActive && status === "running") return theme.primary;
|
|
1166
1201
|
return STATUS_COLOR[status] ?? theme.foreground;
|
|
@@ -1177,7 +1212,7 @@ function formatIterCount(history) {
|
|
|
1177
1212
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
1178
1213
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1179
1214
|
function IterationRow({ record, tick }) {
|
|
1180
|
-
const icon = record.status
|
|
1215
|
+
const icon = statusIcon(record.status, tick);
|
|
1181
1216
|
const color = STATUS_COLOR[record.status] ?? theme.primary;
|
|
1182
1217
|
const innerText = record.inner ? ` \u2014 ${stripItem(record.inner.name, record.item)} [${record.inner.index + 1}/${record.inner.total}]` : "";
|
|
1183
1218
|
const ms = (record.endTime ?? Date.now()) - record.startTime;
|
|
@@ -2056,17 +2091,6 @@ var INIT_STATE = {
|
|
|
2056
2091
|
judgeAttempt: 0,
|
|
2057
2092
|
recentOutput: []
|
|
2058
2093
|
};
|
|
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
2094
|
function appendLog(logFile, text) {
|
|
2071
2095
|
if (logFile) appendFileSync(logFile, text + "\n");
|
|
2072
2096
|
}
|
|
@@ -2138,21 +2162,21 @@ Step failed: ${error.message}
|
|
|
2138
2162
|
finalizeComplexSequence(s);
|
|
2139
2163
|
return s;
|
|
2140
2164
|
}
|
|
2141
|
-
function
|
|
2165
|
+
function buildHighlightHeader(ctx, s, title, extra = []) {
|
|
2142
2166
|
return [
|
|
2143
|
-
|
|
2167
|
+
`# ${title}`,
|
|
2144
2168
|
"",
|
|
2145
2169
|
`**Task:** ${ctx.slug}`,
|
|
2146
2170
|
`**Step:** ${s.stepName}`,
|
|
2171
|
+
...extra,
|
|
2147
2172
|
`**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2148
2173
|
"",
|
|
2149
2174
|
"---",
|
|
2150
|
-
"",
|
|
2151
|
-
"## Claude's Tool Orchestration",
|
|
2152
|
-
"",
|
|
2153
|
-
"Claude used multiple tools to complete this step:",
|
|
2154
2175
|
""
|
|
2155
|
-
].join("\n");
|
|
2176
|
+
].join("\n") + "\n";
|
|
2177
|
+
}
|
|
2178
|
+
function complexSequenceHeader(ctx, s) {
|
|
2179
|
+
return buildHighlightHeader(ctx, s, "Complex Tool Sequence") + "## Claude's Tool Orchestration\n\nClaude used multiple tools to complete this step:\n\n";
|
|
2156
2180
|
}
|
|
2157
2181
|
function createComplexSequenceFile(ctx, s) {
|
|
2158
2182
|
const path = highlightPath(ctx, s.stepIndex, "complex_sequence");
|
|
@@ -2160,7 +2184,7 @@ function createComplexSequenceFile(ctx, s) {
|
|
|
2160
2184
|
return path;
|
|
2161
2185
|
}
|
|
2162
2186
|
function onTool(ctx, s, tool, input) {
|
|
2163
|
-
const desc =
|
|
2187
|
+
const desc = getToolArg(tool, input);
|
|
2164
2188
|
appendLog(s.logFile, ` [${tool}] ${desc}`);
|
|
2165
2189
|
const toolCount = s.toolCount + 1;
|
|
2166
2190
|
const complexSequenceFile = toolCount === 3 ? createComplexSequenceFile(ctx, s) : s.complexSequenceFile;
|
|
@@ -2176,23 +2200,9 @@ function onTool(ctx, s, tool, input) {
|
|
|
2176
2200
|
function saveJudgeHighlight(ctx, s, verdict, text) {
|
|
2177
2201
|
writeFileSync3(
|
|
2178
2202
|
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")
|
|
2203
|
+
buildHighlightHeader(ctx, s, `Judge Verdict: ${verdict}`, [
|
|
2204
|
+
`**Attempt:** ${s.judgeAttempt}`
|
|
2205
|
+
]) + [text, "", "---", "", "*Auto-captured*", ""].join("\n")
|
|
2196
2206
|
);
|
|
2197
2207
|
}
|
|
2198
2208
|
var LOG_MATCHERS = [
|
|
@@ -2207,19 +2217,11 @@ var LOG_MATCHERS = [
|
|
|
2207
2217
|
},
|
|
2208
2218
|
{
|
|
2209
2219
|
pattern: /\[self-healing\].*failed.*exit\s+(\d+)/i,
|
|
2210
|
-
apply: (ctx, s,
|
|
2220
|
+
apply: (ctx, s, _text, match) => {
|
|
2211
2221
|
const selfHealingFile = highlightPath(ctx, s.stepIndex, "self_healing");
|
|
2212
2222
|
writeFileSync3(
|
|
2213
2223
|
selfHealingFile,
|
|
2214
|
-
[
|
|
2215
|
-
"# Self-Healing Activation",
|
|
2216
|
-
"",
|
|
2217
|
-
`**Task:** ${ctx.slug}`,
|
|
2218
|
-
`**Step:** ${s.stepName}`,
|
|
2219
|
-
`**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2220
|
-
"",
|
|
2221
|
-
"---",
|
|
2222
|
-
"",
|
|
2224
|
+
buildHighlightHeader(ctx, s, "Self-Healing Activation") + [
|
|
2223
2225
|
"## \u274C Failure Detected",
|
|
2224
2226
|
"",
|
|
2225
2227
|
`**Exit Code:** ${match[1]}`,
|
|
@@ -2266,14 +2268,15 @@ var LOG_MATCHERS = [
|
|
|
2266
2268
|
];
|
|
2267
2269
|
function onLogMessage(ctx, s, level, text) {
|
|
2268
2270
|
appendLog(s.logFile, `[${level}] ${text}`);
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2271
|
+
let state = s;
|
|
2272
|
+
for (const { pattern, apply } of LOG_MATCHERS) {
|
|
2273
|
+
const m = pattern.exec(text);
|
|
2274
|
+
if (m) {
|
|
2275
|
+
state = apply(ctx, state, text, m);
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
return state;
|
|
2277
2280
|
}
|
|
2278
2281
|
function onWorkflowComplete(ctx, s) {
|
|
2279
2282
|
appendLog(
|
|
@@ -2596,7 +2599,7 @@ Version: ${CURRENT_VERSION}
|
|
|
2596
2599
|
Options:
|
|
2597
2600
|
--ci Headless mode \u2014 print events as NDJSON, no TUI
|
|
2598
2601
|
--step <name|index> Run only the named step or step at 1-based index
|
|
2599
|
-
--from-step <n>
|
|
2602
|
+
--from-step <n> Resume from step n (e.g. 3, 3.2, 2.5.4.3 \u2014 1-based path)
|
|
2600
2603
|
--help, -h Show this help
|
|
2601
2604
|
|
|
2602
2605
|
Commands:
|
|
@@ -2678,11 +2681,15 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
2678
2681
|
console.error("--from-step requires a value");
|
|
2679
2682
|
process.exit(1);
|
|
2680
2683
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
+
const raw = rawArgs[++i];
|
|
2685
|
+
const parts = raw.split(".").map(Number);
|
|
2686
|
+
if (parts.some(Number.isNaN) || parts.some((p) => p < 1)) {
|
|
2687
|
+
console.error(
|
|
2688
|
+
"--from-step must be N or N.M.K... (all 1-based, e.g. 3 or 3.2 or 2.5.4.3)"
|
|
2689
|
+
);
|
|
2684
2690
|
process.exit(1);
|
|
2685
2691
|
}
|
|
2692
|
+
fromStep = parts;
|
|
2686
2693
|
} else {
|
|
2687
2694
|
positional.push(a);
|
|
2688
2695
|
}
|