prompts-gpt 0.3.2 → 0.3.4
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/README.md +52 -0
- package/dist/cli.js +645 -166
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/model-registry.d.ts +8 -0
- package/dist/model-registry.d.ts.map +1 -0
- package/dist/model-registry.js +76 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/orchestrate.d.ts +58 -0
- package/dist/orchestrate.d.ts.map +1 -1
- package/dist/orchestrate.js +243 -56
- package/dist/orchestrate.js.map +1 -1
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +158 -40
- package/dist/runtime.js.map +1 -1
- package/dist/sweep.d.ts +2 -0
- package/dist/sweep.d.ts.map +1 -1
- package/dist/sweep.js +45 -33
- package/dist/sweep.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,8 @@ import { existsSync, readFileSync, statSync, readdirSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
|
-
import { hasTokenUsage, DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, DEFAULT_RUN_CONFIG_PATH, PROMPTS_GPT_CREDENTIALS_FILE, PromptsGptApiError, PromptsGptClient, doctor, initRunConfig, loadRunConfig, normalizeConcreteProvider, normalizeOrchestrationAgent, ORCHESTRATION_AGENT_PROFILES, runBatch, runPrompt, resolveRunProvider, warnModelProviderMismatch, sweepPrompt, validateRunConfig, discoverWorkspaceAssets, SUPPORTED_AGENT_TARGETS, detectProviders, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptManifest, writePromptMarkdownFiles, ensureGitignoreEntry, isCI, orchestrateParallel, orchestratePipeline, orchestrateEval, captureGitBranch, resolveModelWithWarning, getModelCostTier, estimateTokenCost, } from "./index.js";
|
|
6
|
+
import { hasTokenUsage, DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, DEFAULT_RUN_CONFIG_PATH, PROMPTS_GPT_CREDENTIALS_FILE, PromptsGptApiError, PromptsGptClient, doctor, initRunConfig, loadRunConfig, normalizeConcreteProvider, normalizeOrchestrationAgent, ORCHESTRATION_AGENT_PROFILES, runBatch, runPrompt, resolveRunProvider, warnModelProviderMismatch, sweepPrompt, validateRunConfig, discoverWorkspaceAssets, SUPPORTED_AGENT_TARGETS, detectProviders, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptManifest, writePromptMarkdownFiles, ensureGitignoreEntry, isProcessAlive, isCI, orchestrateParallel, orchestratePipeline, orchestrateEval, captureGitBranch, resolveModelWithWarning, getModelCostTier, estimateTokenCost, DEFAULT_MODELS, } from "./index.js";
|
|
7
|
+
import { PROVIDER_MODELS } from "./model-registry.js";
|
|
7
8
|
const CLI_EXIT_CODES = {
|
|
8
9
|
success: 0,
|
|
9
10
|
general: 1,
|
|
@@ -210,7 +211,7 @@ async function runCommand(command, flags) {
|
|
|
210
211
|
if (setupPicked === "list") {
|
|
211
212
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
212
213
|
const cliEntry = resolveCliEntry();
|
|
213
|
-
spSync(process.execPath, [cliEntry, "list"], { stdio: "inherit", cwd });
|
|
214
|
+
spSync(process.execPath, [cliEntry, "list"], { stdio: "inherit", cwd, shell: false, windowsHide: true });
|
|
214
215
|
}
|
|
215
216
|
else if (setupPicked !== "done") {
|
|
216
217
|
const [action, file] = setupPicked.split(":", 2);
|
|
@@ -218,7 +219,7 @@ async function runCommand(command, flags) {
|
|
|
218
219
|
console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
|
|
219
220
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
220
221
|
const cliEntry = resolveCliEntry();
|
|
221
|
-
const setupResult = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
|
|
222
|
+
const setupResult = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd, shell: false, windowsHide: true });
|
|
222
223
|
process.exitCode = setupResult.status ?? 1;
|
|
223
224
|
}
|
|
224
225
|
}
|
|
@@ -496,12 +497,7 @@ async function runCommand(command, flags) {
|
|
|
496
497
|
const lockContent = JSON.parse(readFileSync(sweepLockPath, "utf8"));
|
|
497
498
|
const lockAge = Date.now() - new Date(lockContent.startedAt ?? 0).getTime();
|
|
498
499
|
const lockAgeHours = lockAge / (60 * 60 * 1000);
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
process.kill(lockContent.pid ?? 0, 0);
|
|
502
|
-
holderAlive = true;
|
|
503
|
-
}
|
|
504
|
-
catch { /* dead */ }
|
|
500
|
+
const holderAlive = isProcessAlive(lockContent.pid);
|
|
505
501
|
if (!holderAlive) {
|
|
506
502
|
console.log(`\n${sym("⚠", "!")} Stale sweep lock detected (PID ${lockContent.pid} is no longer running)`);
|
|
507
503
|
console.log(` Started: ${lockContent.startedAt ?? "unknown"}`);
|
|
@@ -673,7 +669,7 @@ async function runCommand(command, flags) {
|
|
|
673
669
|
console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
|
|
674
670
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
675
671
|
const cliEntry = resolveCliEntry();
|
|
676
|
-
const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
|
|
672
|
+
const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd, shell: false, windowsHide: true });
|
|
677
673
|
process.exitCode = result.status ?? 1;
|
|
678
674
|
}
|
|
679
675
|
}
|
|
@@ -826,6 +822,9 @@ async function runCommand(command, flags) {
|
|
|
826
822
|
}
|
|
827
823
|
if (command === "run") {
|
|
828
824
|
const cwd = getResolvedCwd(flags);
|
|
825
|
+
if (isCI() && !flags["non-interactive"]) {
|
|
826
|
+
flags["non-interactive"] = true;
|
|
827
|
+
}
|
|
829
828
|
const promptFile = getStringFlag(flags, "prompt-file");
|
|
830
829
|
const config = await loadRunConfig(cwd);
|
|
831
830
|
warnOnConfigIssues(config);
|
|
@@ -1014,6 +1013,10 @@ async function runCommand(command, flags) {
|
|
|
1014
1013
|
const doRun = async () => {
|
|
1015
1014
|
if (running)
|
|
1016
1015
|
return;
|
|
1016
|
+
if (!existsSync(resolvedWatch)) {
|
|
1017
|
+
console.log(` File deleted: ${resolvedWatch}. Skipping run.`);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1017
1020
|
running = true;
|
|
1018
1021
|
console.log(`\n${colorize("▶ Running...", "\x1b[36m")} (${new Date().toLocaleTimeString()})`);
|
|
1019
1022
|
try {
|
|
@@ -1041,20 +1044,28 @@ async function runCommand(command, flags) {
|
|
|
1041
1044
|
};
|
|
1042
1045
|
await doRun();
|
|
1043
1046
|
let debounce = null;
|
|
1047
|
+
const closeWatcher = (message) => {
|
|
1048
|
+
if (debounce) {
|
|
1049
|
+
clearTimeout(debounce);
|
|
1050
|
+
debounce = null;
|
|
1051
|
+
}
|
|
1052
|
+
if (message)
|
|
1053
|
+
console.log(message);
|
|
1054
|
+
watcher.close();
|
|
1055
|
+
};
|
|
1044
1056
|
const watcher = fsWatch(resolvedWatch, () => {
|
|
1057
|
+
if (!existsSync(resolvedWatch)) {
|
|
1058
|
+
closeWatcher(` File deleted: ${resolvedWatch}. Exiting watch mode.`);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1045
1061
|
if (debounce)
|
|
1046
1062
|
clearTimeout(debounce);
|
|
1047
1063
|
debounce = setTimeout(doRun, 500);
|
|
1048
1064
|
debounce.unref?.();
|
|
1049
1065
|
});
|
|
1050
1066
|
watcher.on("error", (err) => {
|
|
1051
|
-
if (debounce) {
|
|
1052
|
-
clearTimeout(debounce);
|
|
1053
|
-
debounce = null;
|
|
1054
|
-
}
|
|
1055
1067
|
console.error(`Watch error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1056
|
-
|
|
1057
|
-
watcher.close();
|
|
1068
|
+
closeWatcher("File may have been deleted. Exiting watch mode.");
|
|
1058
1069
|
});
|
|
1059
1070
|
await new Promise((resolve) => {
|
|
1060
1071
|
let settled = false;
|
|
@@ -1141,11 +1152,11 @@ async function runCommand(command, flags) {
|
|
|
1141
1152
|
try {
|
|
1142
1153
|
const { spawn: openSpawn } = await import("node:child_process");
|
|
1143
1154
|
if (process.platform === "win32") {
|
|
1144
|
-
openSpawn("cmd", ["/c", "start", "", result.summaryFile], { detached: true, stdio: "ignore", windowsHide: true }).unref();
|
|
1155
|
+
openSpawn("cmd", ["/c", "start", "", result.summaryFile], { detached: true, stdio: "ignore", windowsHide: true, shell: false }).unref();
|
|
1145
1156
|
}
|
|
1146
1157
|
else {
|
|
1147
1158
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
1148
|
-
openSpawn(openCmd, [result.summaryFile], { detached: true, stdio: "ignore" }).unref();
|
|
1159
|
+
openSpawn(openCmd, [result.summaryFile], { detached: true, stdio: "ignore", shell: false }).unref();
|
|
1149
1160
|
}
|
|
1150
1161
|
}
|
|
1151
1162
|
catch { /* ignore */ }
|
|
@@ -1229,6 +1240,14 @@ async function runCommand(command, flags) {
|
|
|
1229
1240
|
flags["non-interactive"] = true;
|
|
1230
1241
|
}
|
|
1231
1242
|
const sweepPromptFile = getStringFlag(flags, "prompt-file");
|
|
1243
|
+
const sweepFilesFlag = getStringFlag(flags, "sweep-files");
|
|
1244
|
+
const allSweepsFlag = Boolean(flags["all-sweeps"]);
|
|
1245
|
+
if (sweepPromptFile && (sweepFilesFlag || allSweepsFlag)) {
|
|
1246
|
+
throw new CliError("Use either --prompt-file for one sweep, --sweep-files for selected sweeps, or --all-sweeps for every sweep file.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
1247
|
+
}
|
|
1248
|
+
if (sweepFilesFlag && allSweepsFlag) {
|
|
1249
|
+
throw new CliError("Use either --sweep-files or --all-sweeps, not both.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
1250
|
+
}
|
|
1232
1251
|
if (sweepPromptFile && !existsSync(path.resolve(cwd, sweepPromptFile))) {
|
|
1233
1252
|
throw new CliError(`Sweep file not found: ${sweepPromptFile}\n\nCheck the path and try again. Run \`prompts-gpt list\` to see available sweep files.`, CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
1234
1253
|
}
|
|
@@ -1255,7 +1274,7 @@ async function runCommand(command, flags) {
|
|
|
1255
1274
|
}
|
|
1256
1275
|
throw new CliError("No supported provider CLI was found on PATH. Install Codex, Cursor Agent, Claude Code, or Copilot CLI, then run `prompts-gpt doctor`.", CLI_EXIT_CODES.validation, { helpCommand: "providers" });
|
|
1257
1276
|
}
|
|
1258
|
-
if (!sweepPromptFile && !Boolean(flags.json)) {
|
|
1277
|
+
if (!sweepPromptFile && !sweepFilesFlag && !allSweepsFlag && !Boolean(flags.json)) {
|
|
1259
1278
|
const assets = await discoverWorkspaceAssets(cwd);
|
|
1260
1279
|
if (assets.sweeps.length === 1) {
|
|
1261
1280
|
const autoFile = assets.sweeps[0].file;
|
|
@@ -1656,6 +1675,159 @@ async function runCommand(command, flags) {
|
|
|
1656
1675
|
evaluatorModel: getStringFlag(flags, "eval-model") ?? getStringFlag(flags, "evaluator-model"),
|
|
1657
1676
|
criteria: getStringFlag(flags, "eval-criteria")?.split(",").map((c) => c.trim()),
|
|
1658
1677
|
} : undefined;
|
|
1678
|
+
const requestedSweepFiles = await resolveSweepFileSelection(cwd, flags);
|
|
1679
|
+
if (requestedSweepFiles.length > 0) {
|
|
1680
|
+
const rawSweepStrategy = getStringFlag(flags, "sweep-strategy") ?? getStringFlag(flags, "files-mode") ?? "sequential";
|
|
1681
|
+
if (rawSweepStrategy !== "sequential" && rawSweepStrategy !== "parallel") {
|
|
1682
|
+
throw new CliError("--sweep-strategy must be either sequential or parallel.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
1683
|
+
}
|
|
1684
|
+
const rawFileConcurrency = parsePositiveIntFlag(getStringFlag(flags, "file-concurrency"), "file-concurrency");
|
|
1685
|
+
const MAX_FILE_CONCURRENCY = 12;
|
|
1686
|
+
const fileConcurrency = rawSweepStrategy === "parallel"
|
|
1687
|
+
? Math.min(rawFileConcurrency ?? requestedSweepFiles.length, MAX_FILE_CONCURRENCY, requestedSweepFiles.length)
|
|
1688
|
+
: 1;
|
|
1689
|
+
if (rawFileConcurrency && rawFileConcurrency > MAX_FILE_CONCURRENCY) {
|
|
1690
|
+
console.log(`Warning: --file-concurrency ${rawFileConcurrency} capped to ${MAX_FILE_CONCURRENCY} to prevent resource exhaustion.`);
|
|
1691
|
+
}
|
|
1692
|
+
const rawParallelCount = parsePositiveIntFlag(getStringFlag(flags, "parallel"), "parallel");
|
|
1693
|
+
const MAX_PARALLEL_SWEEPS = 16;
|
|
1694
|
+
const parallelCount = rawParallelCount ? Math.min(rawParallelCount, MAX_PARALLEL_SWEEPS) : undefined;
|
|
1695
|
+
if (rawParallelCount && rawParallelCount > MAX_PARALLEL_SWEEPS) {
|
|
1696
|
+
console.log(`Warning: --parallel ${rawParallelCount} capped to ${MAX_PARALLEL_SWEEPS} to prevent resource exhaustion.`);
|
|
1697
|
+
}
|
|
1698
|
+
const explicitIterations = parsePositiveIntFlag(getStringFlag(flags, "iterations"), "iterations");
|
|
1699
|
+
const runBaseId = getStringFlag(flags, "run-id") || `sweep-plan-${new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14)}`;
|
|
1700
|
+
const planStartMs = Date.now();
|
|
1701
|
+
async function resolveIterationsForFile(file) {
|
|
1702
|
+
if (explicitIterations)
|
|
1703
|
+
return explicitIterations;
|
|
1704
|
+
return await readSweepIterationsFromFrontmatter(path.resolve(cwd, file)) ?? 1;
|
|
1705
|
+
}
|
|
1706
|
+
async function runSweepFile(file, index) {
|
|
1707
|
+
const iterationsForFile = await resolveIterationsForFile(file);
|
|
1708
|
+
const fileSlug = path.basename(file, ".md").replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || `file-${index + 1}`;
|
|
1709
|
+
const fileRunId = `${runBaseId}-${String(index + 1).padStart(2, "0")}-${fileSlug}`;
|
|
1710
|
+
if (parallelCount && parallelCount > 1) {
|
|
1711
|
+
const batchSize = Math.min(parallelCount, iterationsForFile);
|
|
1712
|
+
if (!silent) {
|
|
1713
|
+
console.log(`\n${path.basename(file)}: running ${iterationsForFile} independent iteration${iterationsForFile === 1 ? "" : "s"} with parallelism=${batchSize}`);
|
|
1714
|
+
}
|
|
1715
|
+
const allResults = [];
|
|
1716
|
+
for (let batchStart = 0; batchStart < iterationsForFile; batchStart += batchSize) {
|
|
1717
|
+
const batchEnd = Math.min(batchStart + batchSize, iterationsForFile);
|
|
1718
|
+
const batchPromises = [];
|
|
1719
|
+
for (let i = batchStart; i < batchEnd; i++) {
|
|
1720
|
+
batchPromises.push(sweepPrompt({
|
|
1721
|
+
cwd,
|
|
1722
|
+
promptFile: file,
|
|
1723
|
+
agent,
|
|
1724
|
+
model: getStringFlag(flags, "model"),
|
|
1725
|
+
iterations: 1,
|
|
1726
|
+
iterationTimeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout"),
|
|
1727
|
+
maxRetries: parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries"),
|
|
1728
|
+
artifactsDir: getStringFlag(flags, "artifacts-dir"),
|
|
1729
|
+
runId: `${fileRunId}-iter-${i + 1}`,
|
|
1730
|
+
approveMcps: !Boolean(flags["no-approve-mcps"]),
|
|
1731
|
+
sandboxMode: getStringFlag(flags, "sandbox"),
|
|
1732
|
+
phase: getStringFlag(flags, "phase"),
|
|
1733
|
+
dryRun: Boolean(flags["dry-run"]),
|
|
1734
|
+
maxRunDirs: parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs"),
|
|
1735
|
+
summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
|
|
1736
|
+
background: Boolean(flags.background),
|
|
1737
|
+
permissionMode: getStringFlag(flags, "permission-mode"),
|
|
1738
|
+
evalAfterEachIteration: evalConfig,
|
|
1739
|
+
onProgress,
|
|
1740
|
+
}));
|
|
1741
|
+
}
|
|
1742
|
+
const batchResults = await Promise.allSettled(batchPromises);
|
|
1743
|
+
for (const batchResult of batchResults) {
|
|
1744
|
+
if (batchResult.status === "fulfilled")
|
|
1745
|
+
allResults.push(batchResult.value);
|
|
1746
|
+
else if (!Boolean(flags.json))
|
|
1747
|
+
console.error(`Sweep failed for ${file}: ${batchResult.reason instanceof Error ? batchResult.reason.message : String(batchResult.reason)}`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const succeeded = allResults.reduce((sum, result) => sum + result.succeeded, 0);
|
|
1751
|
+
const failed = allResults.reduce((sum, result) => sum + result.failed, 0) + (iterationsForFile - allResults.length);
|
|
1752
|
+
return { file, iterations: iterationsForFile, succeeded, failed, results: allResults };
|
|
1753
|
+
}
|
|
1754
|
+
const result = await sweepPrompt({
|
|
1755
|
+
cwd,
|
|
1756
|
+
promptFile: file,
|
|
1757
|
+
agent,
|
|
1758
|
+
model: getStringFlag(flags, "model"),
|
|
1759
|
+
iterations: iterationsForFile,
|
|
1760
|
+
iterationTimeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout"),
|
|
1761
|
+
maxRetries: parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries"),
|
|
1762
|
+
artifactsDir: getStringFlag(flags, "artifacts-dir"),
|
|
1763
|
+
runId: fileRunId,
|
|
1764
|
+
approveMcps: !Boolean(flags["no-approve-mcps"]),
|
|
1765
|
+
sandboxMode: getStringFlag(flags, "sandbox"),
|
|
1766
|
+
phase: getStringFlag(flags, "phase"),
|
|
1767
|
+
dryRun: Boolean(flags["dry-run"]),
|
|
1768
|
+
maxRunDirs: parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs"),
|
|
1769
|
+
summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
|
|
1770
|
+
background: Boolean(flags.background),
|
|
1771
|
+
permissionMode: getStringFlag(flags, "permission-mode"),
|
|
1772
|
+
evalAfterEachIteration: evalConfig,
|
|
1773
|
+
onProgress,
|
|
1774
|
+
});
|
|
1775
|
+
return { file, iterations: result.totalIterations, succeeded: result.succeeded, failed: result.failed, results: [result] };
|
|
1776
|
+
}
|
|
1777
|
+
if (!Boolean(flags.json)) {
|
|
1778
|
+
console.log(`Running ${requestedSweepFiles.length} sweep file${requestedSweepFiles.length === 1 ? "" : "s"} with ${rawSweepStrategy} file execution${rawSweepStrategy === "parallel" ? ` (file concurrency=${fileConcurrency})` : ""}.`);
|
|
1779
|
+
if (parallelCount && parallelCount > 1) {
|
|
1780
|
+
console.log(`Each file runs its iterations independently with --parallel ${parallelCount}.`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
const plannedResults = [];
|
|
1784
|
+
if (rawSweepStrategy === "parallel") {
|
|
1785
|
+
for (let batchStart = 0; batchStart < requestedSweepFiles.length; batchStart += fileConcurrency) {
|
|
1786
|
+
const batch = requestedSweepFiles.slice(batchStart, batchStart + fileConcurrency);
|
|
1787
|
+
const settled = await Promise.allSettled(batch.map((file, offset) => runSweepFile(file, batchStart + offset)));
|
|
1788
|
+
for (const result of settled) {
|
|
1789
|
+
if (result.status === "fulfilled")
|
|
1790
|
+
plannedResults.push(result.value);
|
|
1791
|
+
else if (!Boolean(flags.json))
|
|
1792
|
+
console.error(result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
for (let index = 0; index < requestedSweepFiles.length; index++) {
|
|
1798
|
+
plannedResults.push(await runSweepFile(requestedSweepFiles[index], index));
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
const totalSucceeded = plannedResults.reduce((sum, result) => sum + result.succeeded, 0);
|
|
1802
|
+
const totalFailed = plannedResults.reduce((sum, result) => sum + result.failed, 0);
|
|
1803
|
+
const totalIterations = plannedResults.reduce((sum, result) => sum + result.iterations, 0);
|
|
1804
|
+
const totalDuration = Date.now() - planStartMs;
|
|
1805
|
+
if (Boolean(flags.json)) {
|
|
1806
|
+
console.log(JSON.stringify({
|
|
1807
|
+
multiFile: true,
|
|
1808
|
+
sweepStrategy: rawSweepStrategy,
|
|
1809
|
+
fileConcurrency,
|
|
1810
|
+
files: plannedResults,
|
|
1811
|
+
totalFiles: requestedSweepFiles.length,
|
|
1812
|
+
totalIterations,
|
|
1813
|
+
totalSucceeded,
|
|
1814
|
+
totalFailed,
|
|
1815
|
+
totalDurationMs: totalDuration,
|
|
1816
|
+
}, null, 2));
|
|
1817
|
+
}
|
|
1818
|
+
else {
|
|
1819
|
+
console.log(`\nSweep plan complete: ${totalSucceeded}/${totalIterations} iterations succeeded across ${requestedSweepFiles.length} file${requestedSweepFiles.length === 1 ? "" : "s"}.`);
|
|
1820
|
+
if (totalFailed > 0)
|
|
1821
|
+
console.log(`Failed iterations: ${totalFailed}`);
|
|
1822
|
+
console.log(`Total wall-clock time: ${formatDuration(totalDuration)}`);
|
|
1823
|
+
for (const result of plannedResults) {
|
|
1824
|
+
console.log(` ${result.file}: ${result.succeeded}/${result.iterations} succeeded`);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
if (totalFailed > 0 || plannedResults.length < requestedSweepFiles.length)
|
|
1828
|
+
process.exitCode = 1;
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1659
1831
|
// Parallel sweep — run N iterations concurrently
|
|
1660
1832
|
const rawParallelCount = parsePositiveIntFlag(getStringFlag(flags, "parallel"), "parallel");
|
|
1661
1833
|
const MAX_PARALLEL_SWEEPS = 16;
|
|
@@ -1804,11 +1976,11 @@ async function runCommand(command, flags) {
|
|
|
1804
1976
|
try {
|
|
1805
1977
|
const { spawn: openSpawn } = await import("node:child_process");
|
|
1806
1978
|
if (process.platform === "win32") {
|
|
1807
|
-
openSpawn("cmd", ["/c", "start", "", result.manifestFile], { detached: true, stdio: "ignore", windowsHide: true }).unref();
|
|
1979
|
+
openSpawn("cmd", ["/c", "start", "", result.manifestFile], { detached: true, stdio: "ignore", windowsHide: true, shell: false }).unref();
|
|
1808
1980
|
}
|
|
1809
1981
|
else {
|
|
1810
1982
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
1811
|
-
openSpawn(openCmd, [result.manifestFile], { detached: true, stdio: "ignore" }).unref();
|
|
1983
|
+
openSpawn(openCmd, [result.manifestFile], { detached: true, stdio: "ignore", shell: false }).unref();
|
|
1812
1984
|
}
|
|
1813
1985
|
}
|
|
1814
1986
|
catch { /* ignore */ }
|
|
@@ -1912,12 +2084,34 @@ async function runCommand(command, flags) {
|
|
|
1912
2084
|
}
|
|
1913
2085
|
else if (event.type === "provider_start") {
|
|
1914
2086
|
const stepLabel = event.step ? ` [${event.step}]` : "";
|
|
1915
|
-
|
|
2087
|
+
const modelLabel = event.model ? colorize(` (${event.model})`, "\x1b[2m") : "";
|
|
2088
|
+
const promptLabel = event.promptFile ? colorize(` ${event.promptFile}`, "\x1b[2m") : "";
|
|
2089
|
+
console.log(` ${orchUc ? "▶" : ">"} ${event.provider}${stepLabel}${modelLabel} starting...${promptLabel}`);
|
|
2090
|
+
}
|
|
2091
|
+
else if (event.type === "step_context") {
|
|
2092
|
+
console.log(` ${colorize(orchUc ? "↳" : "->", "\x1b[36m")} Receiving ${formatContextSize(event.contextChars)} context from "${event.source}"`);
|
|
2093
|
+
if (event.contextPreview) {
|
|
2094
|
+
console.log(` ${colorize(truncatePreview(event.contextPreview, 120), "\x1b[2m")}`);
|
|
2095
|
+
}
|
|
1916
2096
|
}
|
|
1917
2097
|
else if (event.type === "provider_end") {
|
|
1918
2098
|
const icon = event.exitCode === 0 ? colorize(orchUc ? "✓" : "OK", "\x1b[32m") : colorize(orchUc ? "✗" : "FAIL", "\x1b[31m");
|
|
1919
2099
|
console.log(` ${icon} ${event.provider} finished (${formatDuration(event.durationMs)}, exit=${event.exitCode})`);
|
|
1920
2100
|
}
|
|
2101
|
+
else if (event.type === "step_summary") {
|
|
2102
|
+
if (event.filesChanged.length > 0) {
|
|
2103
|
+
console.log(` ${colorize(orchUc ? "📝" : "[files]", "\x1b[36m")} ${event.filesChanged.length} file(s) changed:`);
|
|
2104
|
+
for (const f of event.filesChanged.slice(0, 10)) {
|
|
2105
|
+
console.log(` ${colorize(f, "\x1b[2m")}`);
|
|
2106
|
+
}
|
|
2107
|
+
if (event.filesChanged.length > 10) {
|
|
2108
|
+
console.log(` ${colorize(`...and ${event.filesChanged.length - 10} more`, "\x1b[2m")}`);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
if (event.outputChars > 0) {
|
|
2112
|
+
console.log(` ${colorize(orchUc ? "💬" : "[out]", "\x1b[36m")} Output: ${formatContextSize(event.outputChars)} ${colorize(`→ passed to next step`, "\x1b[2m")}`);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
1921
2115
|
else if (event.type === "eval_start") {
|
|
1922
2116
|
console.log(` ${orchUc && supportsUnicode() ? "🧪" : "[eval]"} Evaluating with ${event.evaluator}...`);
|
|
1923
2117
|
}
|
|
@@ -1927,8 +2121,12 @@ async function runCommand(command, flags) {
|
|
|
1927
2121
|
else if (event.type === "warning") {
|
|
1928
2122
|
console.log(` ${colorize(orchUc ? "⚠" : "[warn]", "\x1b[33m")} ${event.text}`);
|
|
1929
2123
|
}
|
|
2124
|
+
else if (event.type === "info") {
|
|
2125
|
+
console.log(` ${colorize(orchUc ? "ℹ" : "[info]", "\x1b[36m")} ${event.text}`);
|
|
2126
|
+
}
|
|
1930
2127
|
else if (event.type === "orchestrate_end") {
|
|
1931
|
-
|
|
2128
|
+
const artLabel = event.artifactsDir ? ` | logs: ${path.relative(cwd, event.artifactsDir)}` : "";
|
|
2129
|
+
console.log(`\n${colorize(orchBar, "\x1b[35m")} Done in ${formatDuration(event.totalDurationMs)}${event.winner ? ` | winner=${event.winner}` : ""}${artLabel} ${colorize(orchBar, "\x1b[35m")}\n`);
|
|
1932
2130
|
}
|
|
1933
2131
|
};
|
|
1934
2132
|
if (mode === "parallel") {
|
|
@@ -1991,20 +2189,22 @@ async function runCommand(command, flags) {
|
|
|
1991
2189
|
throw new CliError(`Pipeline steps file not found: ${resolvedStepsFile}`, CLI_EXIT_CODES.usage);
|
|
1992
2190
|
}
|
|
1993
2191
|
let stepsJson;
|
|
2192
|
+
let pipelineDefaults = {};
|
|
1994
2193
|
try {
|
|
1995
2194
|
const parsed = JSON.parse(readFileSync(resolvedStepsFile, "utf8"));
|
|
1996
|
-
if (
|
|
2195
|
+
if (isPipelineStepArray(parsed)) {
|
|
1997
2196
|
stepsJson = parsed;
|
|
1998
2197
|
}
|
|
1999
|
-
else if (
|
|
2198
|
+
else if (isPipelineConfigObject(parsed)) {
|
|
2000
2199
|
stepsJson = parsed.steps;
|
|
2200
|
+
pipelineDefaults = parsed.defaults ?? {};
|
|
2001
2201
|
}
|
|
2002
2202
|
else {
|
|
2003
|
-
throw new Error("Pipeline
|
|
2203
|
+
throw new Error("Pipeline config must be an array of steps or an object with { defaults?, steps }.");
|
|
2004
2204
|
}
|
|
2005
2205
|
}
|
|
2006
2206
|
catch (error) {
|
|
2007
|
-
if (error instanceof Error && error.message.includes("Pipeline
|
|
2207
|
+
if (error instanceof Error && error.message.includes("Pipeline config must be")) {
|
|
2008
2208
|
throw new CliError(error.message, CLI_EXIT_CODES.validation);
|
|
2009
2209
|
}
|
|
2010
2210
|
throw new CliError(`Pipeline steps file contains invalid JSON: ${resolvedStepsFile}`, CLI_EXIT_CODES.validation);
|
|
@@ -2012,15 +2212,22 @@ async function runCommand(command, flags) {
|
|
|
2012
2212
|
if (!Array.isArray(stepsJson) || stepsJson.length === 0) {
|
|
2013
2213
|
throw new CliError("Pipeline steps file must contain a non-empty JSON array", CLI_EXIT_CODES.usage);
|
|
2014
2214
|
}
|
|
2015
|
-
const defaultPipelineAgent = resolveRunAgent(flags, config.defaultAgent);
|
|
2016
|
-
const globalPipelineModel = getStringFlag(flags, "model");
|
|
2215
|
+
const defaultPipelineAgent = pipelineDefaults.agent?.trim() || resolveRunAgent(flags, config.defaultAgent);
|
|
2216
|
+
const globalPipelineModel = getStringFlag(flags, "model") || pipelineDefaults.model?.trim();
|
|
2217
|
+
const globalPipelineTimeout = pipelineDefaults.timeout;
|
|
2218
|
+
const globalPipelineRetries = pipelineDefaults.retries;
|
|
2219
|
+
const globalPipelineSandbox = pipelineDefaults.sandboxMode;
|
|
2017
2220
|
for (const step of stepsJson) {
|
|
2018
2221
|
if (!step || typeof step.name !== "string" || typeof step.promptFile !== "string") {
|
|
2019
2222
|
throw new CliError(`Pipeline step entries must include name and promptFile fields: ${resolvedStepsFile}`, CLI_EXIT_CODES.validation);
|
|
2020
2223
|
}
|
|
2021
|
-
|
|
2224
|
+
const stepPromptPath = path.resolve(cwd, step.promptFile);
|
|
2225
|
+
if (!existsSync(stepPromptPath)) {
|
|
2022
2226
|
throw new CliError(`Pipeline step "${step.name}" references missing prompt file: ${step.promptFile}`, CLI_EXIT_CODES.usage);
|
|
2023
2227
|
}
|
|
2228
|
+
if (!statSync(stepPromptPath).isFile()) {
|
|
2229
|
+
throw new CliError(`Pipeline step "${step.name}" promptFile is not a file: ${step.promptFile}`, CLI_EXIT_CODES.usage);
|
|
2230
|
+
}
|
|
2024
2231
|
const effectiveAgent = typeof step.agent === "string" && step.agent.trim() ? step.agent : defaultPipelineAgent;
|
|
2025
2232
|
normalizeOrchestrationAgent(effectiveAgent);
|
|
2026
2233
|
step.agent = effectiveAgent;
|
|
@@ -2030,6 +2237,15 @@ async function runCommand(command, flags) {
|
|
|
2030
2237
|
if (!step.model && globalPipelineModel) {
|
|
2031
2238
|
step.model = globalPipelineModel;
|
|
2032
2239
|
}
|
|
2240
|
+
if (!step.timeout && globalPipelineTimeout) {
|
|
2241
|
+
step.timeout = globalPipelineTimeout;
|
|
2242
|
+
}
|
|
2243
|
+
if (typeof step.retries !== "number" && typeof globalPipelineRetries === "number") {
|
|
2244
|
+
step.retries = globalPipelineRetries;
|
|
2245
|
+
}
|
|
2246
|
+
if (!step.sandboxMode && globalPipelineSandbox) {
|
|
2247
|
+
step.sandboxMode = globalPipelineSandbox;
|
|
2248
|
+
}
|
|
2033
2249
|
if (step.model) {
|
|
2034
2250
|
const stepProvider = resolveRunProvider(normalizeOrchestrationAgent(step.agent), providers, config.providerOrder);
|
|
2035
2251
|
const modelCheck = validateModelForProvider(step.model, stepProvider);
|
|
@@ -2038,11 +2254,40 @@ async function runCommand(command, flags) {
|
|
|
2038
2254
|
}
|
|
2039
2255
|
}
|
|
2040
2256
|
}
|
|
2257
|
+
if (isTTYInteractive(flags) && !jsonOutput && !globalPipelineModel) {
|
|
2258
|
+
for (const step of stepsJson) {
|
|
2259
|
+
if (!step.model) {
|
|
2260
|
+
const stepProvider = resolveRunProvider(normalizeOrchestrationAgent(step.agent ?? defaultPipelineAgent), providers, config.providerOrder);
|
|
2261
|
+
const modelChoices = getModelChoicesForProvider(stepProvider, config);
|
|
2262
|
+
const defaultModel = config.modelOverrides[stepProvider] || DEFAULT_MODELS[stepProvider] || "auto";
|
|
2263
|
+
modelChoices.unshift({ label: `${defaultModel} (default)`, value: defaultModel });
|
|
2264
|
+
const pickedModel = await interactiveSelect(`Model for step "${step.name}" (${stepProvider}):`, modelChoices.filter((c, i, arr) => arr.findIndex((x) => x.value === c.value) === i));
|
|
2265
|
+
if (pickedModel === "__custom__") {
|
|
2266
|
+
step.model = await interactiveInput("Enter custom model name", "");
|
|
2267
|
+
}
|
|
2268
|
+
else {
|
|
2269
|
+
step.model = pickedModel;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
if (!jsonOutput && !dryRun) {
|
|
2275
|
+
console.log(`\n Pipeline Plan (${stepsJson.length} steps):`);
|
|
2276
|
+
for (let idx = 0; idx < stepsJson.length; idx++) {
|
|
2277
|
+
const s = stepsJson[idx];
|
|
2278
|
+
const sp = resolveRunProvider(normalizeOrchestrationAgent(s.agent ?? defaultPipelineAgent), providers, config.providerOrder);
|
|
2279
|
+
const sm = s.model?.trim() || config.modelOverrides[sp] || DEFAULT_MODELS[sp] || "auto";
|
|
2280
|
+
console.log(` ${idx + 1}. ${s.name} ${colorize(`(${sp} / ${sm})`, "\x1b[2m")}`);
|
|
2281
|
+
}
|
|
2282
|
+
console.log();
|
|
2283
|
+
}
|
|
2284
|
+
const continueOnError = Boolean(flags["continue-on-error"]);
|
|
2041
2285
|
const result = await orchestratePipeline({
|
|
2042
2286
|
cwd,
|
|
2043
2287
|
steps: stepsJson,
|
|
2044
2288
|
timeoutSeconds,
|
|
2045
2289
|
dryRun,
|
|
2290
|
+
continueOnError,
|
|
2046
2291
|
onProgress,
|
|
2047
2292
|
});
|
|
2048
2293
|
if (jsonOutput) {
|
|
@@ -2052,12 +2297,25 @@ async function runCommand(command, flags) {
|
|
|
2052
2297
|
console.log("Pipeline Results:");
|
|
2053
2298
|
for (const step of result.steps) {
|
|
2054
2299
|
const icon = step.result.exitCode === 0 ? sym("✓", "+") : sym("✗", "x");
|
|
2055
|
-
const
|
|
2056
|
-
console.log(` ${icon} ${step.name} (${step.provider}${
|
|
2300
|
+
const modelStr = step.result.model && step.result.model !== "dry-run" ? colorize(` model=${step.result.model}`, "\x1b[2m") : "";
|
|
2301
|
+
console.log(` ${icon} ${step.name} (${step.provider}${modelStr}) — ${formatDuration(step.durationMs)}`);
|
|
2302
|
+
if (step.result.summaryFile && existsSync(step.result.summaryFile)) {
|
|
2303
|
+
const summary = readFileSync(step.result.summaryFile, "utf8").trim();
|
|
2304
|
+
if (summary) {
|
|
2305
|
+
const summaryPreview = truncatePreview(summary.replace(/\n/g, " "), 150);
|
|
2306
|
+
console.log(` ${colorize(summaryPreview, "\x1b[2m")}`);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2057
2309
|
}
|
|
2058
2310
|
const successCount = result.steps.filter((s) => s.result.exitCode === 0).length;
|
|
2059
2311
|
const failCount = result.steps.filter((s) => s.result.exitCode !== 0).length;
|
|
2060
2312
|
console.log(`\n Total: ${formatDuration(result.totalDurationMs)} | ${successCount} passed${failCount > 0 ? `, ${failCount} failed` : ""}`);
|
|
2313
|
+
if (result.artifactsDir) {
|
|
2314
|
+
const relativeArtifacts = path.relative(cwd, result.artifactsDir) || result.artifactsDir;
|
|
2315
|
+
console.log(` Artifacts: ${colorize(relativeArtifacts, "\x1b[2m")}`);
|
|
2316
|
+
console.log(`\n Inspect step details:`);
|
|
2317
|
+
console.log(` prompts-gpt diff ${relativeArtifacts}`);
|
|
2318
|
+
}
|
|
2061
2319
|
}
|
|
2062
2320
|
return;
|
|
2063
2321
|
}
|
|
@@ -2195,7 +2453,19 @@ async function runCommand(command, flags) {
|
|
|
2195
2453
|
if (!existsSync(runDir)) {
|
|
2196
2454
|
throw new CliError(`Run directory not found: ${runDir}`, CLI_EXIT_CODES.usage);
|
|
2197
2455
|
}
|
|
2198
|
-
const { readFile: fsRead } = await import("node:fs/promises");
|
|
2456
|
+
const { lstat: fsLstat, readFile: fsRead, realpath: fsRealpath } = await import("node:fs/promises");
|
|
2457
|
+
const runDirStat = await fsLstat(runDir).catch((error) => {
|
|
2458
|
+
throw new CliError(`Cannot inspect run directory ${runDir}: ${error instanceof Error ? error.message : String(error)}`, CLI_EXIT_CODES.usage);
|
|
2459
|
+
});
|
|
2460
|
+
if (!runDirStat.isDirectory() || runDirStat.isSymbolicLink()) {
|
|
2461
|
+
throw new CliError(`Invalid run-id: ${runId} is not a regular run directory.`, CLI_EXIT_CODES.usage);
|
|
2462
|
+
}
|
|
2463
|
+
const [realRunDir, realArtifactsDir] = await Promise.all([fsRealpath(runDir), fsRealpath(artifactsDir)]);
|
|
2464
|
+
const realRunDirCheck = caseInsensitive ? realRunDir.toLowerCase() : realRunDir;
|
|
2465
|
+
const realArtifactsDirCheck = caseInsensitive ? realArtifactsDir.toLowerCase() : realArtifactsDir;
|
|
2466
|
+
if (!realRunDirCheck.startsWith(realArtifactsDirCheck + path.sep) && realRunDirCheck !== realArtifactsDirCheck) {
|
|
2467
|
+
throw new CliError("Invalid run-id: resolved path escapes the artifacts directory.", CLI_EXIT_CODES.usage);
|
|
2468
|
+
}
|
|
2199
2469
|
const deltaFile = path.join(runDir, "worktree-delta.diff");
|
|
2200
2470
|
if (existsSync(deltaFile)) {
|
|
2201
2471
|
const delta = await fsRead(deltaFile, "utf8");
|
|
@@ -2238,7 +2508,7 @@ async function runCommand(command, flags) {
|
|
|
2238
2508
|
console.log("======================");
|
|
2239
2509
|
console.log("");
|
|
2240
2510
|
const { spawnSync: gitCheck } = await import("node:child_process");
|
|
2241
|
-
const gitResult = gitCheck("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
2511
|
+
const gitResult = gitCheck("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8", timeout: 5000, windowsHide: true, shell: false });
|
|
2242
2512
|
if (gitResult.status !== 0) {
|
|
2243
2513
|
console.log(`${sym("⚠", "!")} Not inside a git repository. Prompts-GPT works best in a git repo for worktree tracking.`);
|
|
2244
2514
|
console.log(" Run: git init\n");
|
|
@@ -2373,7 +2643,7 @@ async function runCommand(command, flags) {
|
|
|
2373
2643
|
console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
|
|
2374
2644
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
2375
2645
|
const cliEntry = resolveCliEntry();
|
|
2376
|
-
const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
|
|
2646
|
+
const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd, shell: false, windowsHide: true });
|
|
2377
2647
|
process.exitCode = result.status ?? 1;
|
|
2378
2648
|
return;
|
|
2379
2649
|
}
|
|
@@ -2511,7 +2781,7 @@ async function runCommand(command, flags) {
|
|
|
2511
2781
|
if (runQs.toLowerCase() !== "n" && runQs.toLowerCase() !== "no") {
|
|
2512
2782
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
2513
2783
|
const cliEntry = resolveCliEntry();
|
|
2514
|
-
const qsResult = spSync(process.execPath, [cliEntry, "quickstart", "--cwd", cwd], { stdio: "inherit", cwd });
|
|
2784
|
+
const qsResult = spSync(process.execPath, [cliEntry, "quickstart", "--cwd", cwd], { stdio: "inherit", cwd, shell: false, windowsHide: true });
|
|
2515
2785
|
process.exitCode = qsResult.status ?? 1;
|
|
2516
2786
|
return;
|
|
2517
2787
|
}
|
|
@@ -2619,7 +2889,7 @@ async function runCommand(command, flags) {
|
|
|
2619
2889
|
console.log(`\nRunning: prompts-gpt run -f ${genFile}\n`);
|
|
2620
2890
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
2621
2891
|
const cliEntry = resolveCliEntry();
|
|
2622
|
-
const genResult = spSync(process.execPath, [cliEntry, "run", "-f", genFile], { stdio: "inherit", cwd });
|
|
2892
|
+
const genResult = spSync(process.execPath, [cliEntry, "run", "-f", genFile], { stdio: "inherit", cwd, shell: false, windowsHide: true });
|
|
2623
2893
|
process.exitCode = genResult.status ?? 1;
|
|
2624
2894
|
}
|
|
2625
2895
|
}
|
|
@@ -2704,20 +2974,90 @@ async function runCommand(command, flags) {
|
|
|
2704
2974
|
if (existsSync(filePath) && !Boolean(flags.overwrite)) {
|
|
2705
2975
|
throw new CliError(`Orchestration file already exists: ${filePath}\nUse --overwrite to replace it.`, CLI_EXIT_CODES.validation);
|
|
2706
2976
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2977
|
+
if (isPipelineTemplate) {
|
|
2978
|
+
const stepsDir = path.join(orchDir, "pipeline-steps");
|
|
2979
|
+
await fsMkdir(stepsDir, { recursive: true });
|
|
2980
|
+
let numSteps = 3;
|
|
2981
|
+
let stepDefs = [
|
|
2982
|
+
{ name: "research", description: `Research and analyze requirements for: ${goal.trim()}`, agent: "codex" },
|
|
2983
|
+
{ name: "implement", description: `Implement the solution based on the research analysis`, agent: "claude" },
|
|
2984
|
+
{ name: "test-and-review", description: `Test, review, and validate the implementation`, agent: "cursor" },
|
|
2985
|
+
];
|
|
2986
|
+
if (isTTYInteractive()) {
|
|
2987
|
+
const stepCountChoice = await interactiveSelect("How many pipeline steps?", [
|
|
2988
|
+
{ label: "2 steps (research + implement)", value: "2" },
|
|
2989
|
+
{ label: "3 steps (research + implement + review)", value: "3" },
|
|
2990
|
+
{ label: "4 steps (research + implement + test + review)", value: "4" },
|
|
2991
|
+
]);
|
|
2992
|
+
numSteps = parseInt(stepCountChoice, 10);
|
|
2993
|
+
if (numSteps === 2) {
|
|
2994
|
+
stepDefs = [
|
|
2995
|
+
{ name: "research", description: `Research and analyze: ${goal.trim()}`, agent: "codex" },
|
|
2996
|
+
{ name: "implement", description: `Implement based on research`, agent: "claude" },
|
|
2997
|
+
];
|
|
2998
|
+
}
|
|
2999
|
+
else if (numSteps === 4) {
|
|
3000
|
+
stepDefs = [
|
|
3001
|
+
{ name: "research", description: `Research and analyze: ${goal.trim()}`, agent: "codex" },
|
|
3002
|
+
{ name: "implement", description: `Implement the solution`, agent: "claude" },
|
|
3003
|
+
{ name: "test", description: `Run tests and fix failures`, agent: "cursor" },
|
|
3004
|
+
{ name: "review", description: `Code review and final validation`, agent: "codex" },
|
|
3005
|
+
];
|
|
3006
|
+
}
|
|
3007
|
+
for (let si = 0; si < stepDefs.length; si++) {
|
|
3008
|
+
const s = stepDefs[si];
|
|
3009
|
+
const customName = await interactiveInput(`Step ${si + 1} name`, s.name);
|
|
3010
|
+
s.name = customName || s.name;
|
|
3011
|
+
const agentPick = await interactiveSelect(`Step ${si + 1} ("${s.name}") agent:`, [
|
|
3012
|
+
{ label: "codex — OpenAI Codex CLI", value: "codex" },
|
|
3013
|
+
{ label: "claude — Anthropic Claude Code", value: "claude" },
|
|
3014
|
+
{ label: "cursor — Cursor Agent", value: "cursor" },
|
|
3015
|
+
{ label: "copilot — GitHub Copilot", value: "copilot" },
|
|
3016
|
+
]);
|
|
3017
|
+
s.agent = agentPick;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
const stepFilePaths = [];
|
|
3021
|
+
for (const s of stepDefs) {
|
|
3022
|
+
const stepFileName = `${s.name.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase()}.md`;
|
|
3023
|
+
const stepFilePath = path.join(stepsDir, stepFileName);
|
|
3024
|
+
stepFilePaths.push(path.relative(cwd, stepFilePath));
|
|
3025
|
+
if (!existsSync(stepFilePath)) {
|
|
3026
|
+
const stepContent = buildPipelineStepPrompt(s.name, s.description, stepDefs.indexOf(s), stepDefs.length);
|
|
3027
|
+
await fsWriteFile(stepFilePath, stepContent);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
const pipelineConfig = {
|
|
3031
|
+
defaults: { timeout: 900 },
|
|
3032
|
+
steps: stepDefs.map((s, si) => ({
|
|
3033
|
+
name: s.name,
|
|
3034
|
+
promptFile: stepFilePaths[si],
|
|
3035
|
+
agent: s.agent,
|
|
3036
|
+
})),
|
|
3037
|
+
};
|
|
3038
|
+
await fsWriteFile(filePath, JSON.stringify(pipelineConfig, null, 2) + "\n");
|
|
3039
|
+
console.log(`${sym("\u2713", "+")} Created pipeline orchestration:`);
|
|
3040
|
+
console.log(` Config: ${path.relative(cwd, filePath)}`);
|
|
3041
|
+
for (const fp of stepFilePaths) {
|
|
3042
|
+
console.log(` Prompt: ${fp}`);
|
|
3043
|
+
}
|
|
3044
|
+
console.log(`\nRun it with:`);
|
|
3045
|
+
console.log(` prompts-gpt orchestrate --mode pipeline --steps ${path.relative(cwd, filePath)}`);
|
|
3046
|
+
console.log(`\nEdit the step prompt files to customize behavior, then run the pipeline.`);
|
|
3047
|
+
}
|
|
3048
|
+
else {
|
|
3049
|
+
const content = ORCHESTRATION_TEMPLATE
|
|
2711
3050
|
.replace(/\{title\}/g, title.trim())
|
|
2712
3051
|
.replace(/\{description\}/g, (description || title).trim())
|
|
2713
3052
|
.replace('"parallel"', `"${mode}"`)
|
|
2714
3053
|
.replace("{goal}", goal.trim())
|
|
2715
3054
|
.replace("{step1}", `Analyze the requirements for: ${goal.trim()}`)
|
|
2716
3055
|
.replace("{step2}", `Implement the solution based on the analysis`);
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
3056
|
+
await fsWriteFile(filePath, content);
|
|
3057
|
+
console.log(`${sym("\u2713", "+")} Created orchestration file: ${filePath}`);
|
|
3058
|
+
console.log(`\nRun it with:`);
|
|
3059
|
+
console.log(` prompts-gpt orchestrate --mode ${mode} -f ${path.relative(cwd, filePath)}`);
|
|
3060
|
+
}
|
|
2721
3061
|
return;
|
|
2722
3062
|
}
|
|
2723
3063
|
if (command === "sync") {
|
|
@@ -2837,7 +3177,7 @@ async function runCommand(command, flags) {
|
|
|
2837
3177
|
const { spawnSync: spSync } = await import("node:child_process");
|
|
2838
3178
|
const cliEntry = resolveCliEntry();
|
|
2839
3179
|
const projCwd = getResolvedCwd(flags);
|
|
2840
|
-
const syncResult = spSync(process.execPath, [cliEntry, "sync", "--cwd", projCwd], { stdio: "inherit", cwd: projCwd });
|
|
3180
|
+
const syncResult = spSync(process.execPath, [cliEntry, "sync", "--cwd", projCwd], { stdio: "inherit", cwd: projCwd, shell: false, windowsHide: true });
|
|
2841
3181
|
process.exitCode = syncResult.status ?? 1;
|
|
2842
3182
|
}
|
|
2843
3183
|
}
|
|
@@ -3124,6 +3464,11 @@ function getCommandOptions(command) {
|
|
|
3124
3464
|
quiet: { type: "boolean", short: "q" },
|
|
3125
3465
|
silent: { type: "boolean" },
|
|
3126
3466
|
parallel: { type: "string" },
|
|
3467
|
+
"all-sweeps": { type: "boolean" },
|
|
3468
|
+
"sweep-files": { type: "string" },
|
|
3469
|
+
"sweep-strategy": { type: "string" },
|
|
3470
|
+
"files-mode": { type: "string" },
|
|
3471
|
+
"file-concurrency": { type: "string" },
|
|
3127
3472
|
eval: { type: "boolean" },
|
|
3128
3473
|
"eval-criteria": { type: "string" },
|
|
3129
3474
|
"eval-agent": { type: "string" },
|
|
@@ -3151,6 +3496,7 @@ function getCommandOptions(command) {
|
|
|
3151
3496
|
model: { type: "string" },
|
|
3152
3497
|
timeout: { type: "string" },
|
|
3153
3498
|
"dry-run": { type: "boolean" },
|
|
3499
|
+
"continue-on-error": { type: "boolean" },
|
|
3154
3500
|
"non-interactive": { type: "boolean" },
|
|
3155
3501
|
"list-models": { type: "boolean" },
|
|
3156
3502
|
};
|
|
@@ -3426,6 +3772,7 @@ function readTokenFromPrompt(command) {
|
|
|
3426
3772
|
stdin.setRawMode?.(true);
|
|
3427
3773
|
}
|
|
3428
3774
|
catch {
|
|
3775
|
+
cleanup();
|
|
3429
3776
|
reject(new CliError("Cannot enable raw mode for token prompt. Use --token-stdin instead.", CLI_EXIT_CODES.usage, { helpCommand: command }));
|
|
3430
3777
|
return;
|
|
3431
3778
|
}
|
|
@@ -3513,15 +3860,54 @@ async function applyDoctorFixes(cwd) {
|
|
|
3513
3860
|
skipped.push(`${DEFAULT_PROMPTS_GPT_OUT_DIR}/ directory already exists`);
|
|
3514
3861
|
}
|
|
3515
3862
|
try {
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3863
|
+
const gitignoreEntries = [
|
|
3864
|
+
".prompts-gpt/.credentials.json",
|
|
3865
|
+
".prompts-gpt/.models.json",
|
|
3866
|
+
".scripts/runs",
|
|
3867
|
+
".sweep.lock",
|
|
3868
|
+
];
|
|
3869
|
+
const gitignoreUpdates = [];
|
|
3870
|
+
for (const entry of gitignoreEntries) {
|
|
3871
|
+
gitignoreUpdates.push(await ensureGitignoreEntry(cwd, entry));
|
|
3872
|
+
}
|
|
3873
|
+
if (gitignoreUpdates.some(Boolean)) {
|
|
3874
|
+
applied.push("Updated .gitignore with sensitive file and sweep artifact patterns");
|
|
3875
|
+
}
|
|
3876
|
+
else {
|
|
3877
|
+
skipped.push(".gitignore already contains sensitive file and sweep artifact patterns");
|
|
3878
|
+
}
|
|
3521
3879
|
}
|
|
3522
3880
|
catch {
|
|
3523
3881
|
failed.push("Could not update .gitignore with sensitive file and sweep artifact patterns");
|
|
3524
3882
|
}
|
|
3883
|
+
const sweepLockPath = path.resolve(cwd, ".sweep.lock");
|
|
3884
|
+
if (existsSync(sweepLockPath)) {
|
|
3885
|
+
try {
|
|
3886
|
+
const lockRaw = readFileSync(sweepLockPath, "utf8");
|
|
3887
|
+
const lockData = JSON.parse(lockRaw);
|
|
3888
|
+
const lockAge = Date.now() - new Date(lockData.startedAt ?? 0).getTime();
|
|
3889
|
+
const lockAgeHours = lockAge / (60 * 60 * 1000);
|
|
3890
|
+
const holderAlive = isProcessAlive(lockData.pid);
|
|
3891
|
+
if (!holderAlive || (Number.isFinite(lockAgeHours) && lockAgeHours >= 4)) {
|
|
3892
|
+
const { rm: fsRm } = await import("node:fs/promises");
|
|
3893
|
+
await fsRm(sweepLockPath, { force: true });
|
|
3894
|
+
applied.push("Removed stale sweep lock");
|
|
3895
|
+
}
|
|
3896
|
+
else {
|
|
3897
|
+
skipped.push("Sweep lock is active (held by running process)");
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
catch {
|
|
3901
|
+
try {
|
|
3902
|
+
const { rm: fsRm } = await import("node:fs/promises");
|
|
3903
|
+
await fsRm(sweepLockPath, { force: true });
|
|
3904
|
+
applied.push("Removed corrupt sweep lock file");
|
|
3905
|
+
}
|
|
3906
|
+
catch {
|
|
3907
|
+
failed.push("Could not remove corrupt sweep lock file");
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3525
3911
|
return { applied, skipped, failed };
|
|
3526
3912
|
}
|
|
3527
3913
|
function getStringFlag(flags, name) {
|
|
@@ -3604,6 +3990,64 @@ function formatDuration(ms) {
|
|
|
3604
3990
|
return `${minutes}m ${seconds}s`;
|
|
3605
3991
|
return `${seconds}s`;
|
|
3606
3992
|
}
|
|
3993
|
+
function formatContextSize(chars) {
|
|
3994
|
+
if (chars < 1000)
|
|
3995
|
+
return `${chars} chars`;
|
|
3996
|
+
if (chars < 100_000)
|
|
3997
|
+
return `${(chars / 1000).toFixed(1)}k chars`;
|
|
3998
|
+
return `${(chars / 1_000_000).toFixed(2)}M chars`;
|
|
3999
|
+
}
|
|
4000
|
+
function truncatePreview(text, maxLen) {
|
|
4001
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
4002
|
+
if (clean.length <= maxLen)
|
|
4003
|
+
return clean;
|
|
4004
|
+
return clean.slice(0, maxLen - 3) + "...";
|
|
4005
|
+
}
|
|
4006
|
+
function isPlainRecord(value) {
|
|
4007
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4008
|
+
}
|
|
4009
|
+
function isOptionalString(value) {
|
|
4010
|
+
return value === undefined || typeof value === "string";
|
|
4011
|
+
}
|
|
4012
|
+
function isOptionalNumber(value) {
|
|
4013
|
+
return value === undefined || (typeof value === "number" && Number.isFinite(value));
|
|
4014
|
+
}
|
|
4015
|
+
function isStringRecord(value) {
|
|
4016
|
+
if (value === undefined)
|
|
4017
|
+
return true;
|
|
4018
|
+
if (!isPlainRecord(value))
|
|
4019
|
+
return false;
|
|
4020
|
+
return Object.entries(value).every(([key, entry]) => key !== "__proto__" && key !== "constructor" && key !== "prototype" && typeof entry === "string");
|
|
4021
|
+
}
|
|
4022
|
+
function isPipelineStepEntry(value) {
|
|
4023
|
+
if (!isPlainRecord(value))
|
|
4024
|
+
return false;
|
|
4025
|
+
return typeof value.name === "string" &&
|
|
4026
|
+
typeof value.promptFile === "string" &&
|
|
4027
|
+
isOptionalString(value.agent) &&
|
|
4028
|
+
isOptionalString(value.model) &&
|
|
4029
|
+
isOptionalNumber(value.timeout) &&
|
|
4030
|
+
isOptionalNumber(value.retries) &&
|
|
4031
|
+
isOptionalString(value.sandboxMode) &&
|
|
4032
|
+
isStringRecord(value.env);
|
|
4033
|
+
}
|
|
4034
|
+
function isPipelineStepArray(value) {
|
|
4035
|
+
return Array.isArray(value) && value.every(isPipelineStepEntry);
|
|
4036
|
+
}
|
|
4037
|
+
function isPipelineDefaults(value) {
|
|
4038
|
+
if (value === undefined)
|
|
4039
|
+
return true;
|
|
4040
|
+
if (!isPlainRecord(value))
|
|
4041
|
+
return false;
|
|
4042
|
+
return isOptionalString(value.agent) &&
|
|
4043
|
+
isOptionalString(value.model) &&
|
|
4044
|
+
isOptionalNumber(value.timeout) &&
|
|
4045
|
+
isOptionalNumber(value.retries) &&
|
|
4046
|
+
isOptionalString(value.sandboxMode);
|
|
4047
|
+
}
|
|
4048
|
+
function isPipelineConfigObject(value) {
|
|
4049
|
+
return isPlainRecord(value) && isPipelineStepArray(value.steps) && isPipelineDefaults(value.defaults);
|
|
4050
|
+
}
|
|
3607
4051
|
function formatList(values) {
|
|
3608
4052
|
if (!Array.isArray(values) || values.length === 0)
|
|
3609
4053
|
return "None";
|
|
@@ -3814,82 +4258,6 @@ async function saveLastUsedModel(cwd, provider, model) {
|
|
|
3814
4258
|
}
|
|
3815
4259
|
const CODEX_API_KEY_ENV_NAMES = ["OPENAI_API_KEY", "CODEX_API_KEY", "OPENAI_API_KEY_FILE"];
|
|
3816
4260
|
const CODEX_CHATGPT_UNSUPPORTED_MODELS = new Set(["gpt-5.5-pro", "gpt-5.4-pro", "gpt-5.5-high-fast"]);
|
|
3817
|
-
const PROVIDER_MODELS = Object.freeze({
|
|
3818
|
-
codex: [
|
|
3819
|
-
{ value: "gpt-5.5", label: "gpt-5.5 — frontier coding & reasoning", tier: "frontier" },
|
|
3820
|
-
{ value: "gpt-5.5-pro", label: "gpt-5.5-pro — smarter, more precise", tier: "frontier" },
|
|
3821
|
-
{ value: "gpt-5.4", label: "gpt-5.4 — strong coding model", tier: "standard" },
|
|
3822
|
-
{ value: "gpt-5.4-pro", label: "gpt-5.4-pro — enhanced responses", tier: "standard" },
|
|
3823
|
-
{ value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast coding & subagents", tier: "fast" },
|
|
3824
|
-
{ value: "gpt-5.4-nano", label: "gpt-5.4-nano — cheapest high-volume", tier: "budget" },
|
|
3825
|
-
{ value: "gpt-5.3-codex", label: "gpt-5.3-codex — merged GPT-5 + Codex", tier: "standard" },
|
|
3826
|
-
{ value: "gpt-5.3-codex-spark", label: "gpt-5.3-codex-spark — 15x faster gen", tier: "fast" },
|
|
3827
|
-
{ value: "o3", label: "o3 — advanced reasoning", tier: "frontier" },
|
|
3828
|
-
],
|
|
3829
|
-
claude: [
|
|
3830
|
-
{ value: "claude-opus-4-7", label: "claude-opus-4-7 — most capable model", tier: "frontier" },
|
|
3831
|
-
{ value: "claude-opus-4-6", label: "claude-opus-4-6 — previous gen opus", tier: "frontier" },
|
|
3832
|
-
{ value: "claude-sonnet-4-6", label: "claude-sonnet-4-6 — balanced default", tier: "standard" },
|
|
3833
|
-
{ value: "claude-sonnet-4-5", label: "claude-sonnet-4-5 — previous gen sonnet", tier: "standard" },
|
|
3834
|
-
{ value: "claude-3-5-haiku", label: "claude-3-5-haiku — fastest & cheapest", tier: "fast" },
|
|
3835
|
-
{ value: "claude-haiku-4-5", label: "claude-haiku-4-5 — fast haiku", tier: "fast" },
|
|
3836
|
-
],
|
|
3837
|
-
cursor: [
|
|
3838
|
-
{ value: "auto", label: "auto — Cursor auto-selects best", tier: "standard" },
|
|
3839
|
-
{ value: "claude-4.7-opus", label: "claude-4.7-opus — Claude Opus 4.7", tier: "frontier" },
|
|
3840
|
-
{ value: "claude-4.7-opus-fast", label: "claude-4.7-opus-fast — Claude Opus 4.7 Fast", tier: "frontier" },
|
|
3841
|
-
{ value: "claude-4.6-opus-high", label: "claude-4.6-opus-high — Claude Opus 4.6 High", tier: "frontier" },
|
|
3842
|
-
{ value: "claude-4.6-opus-high-thinking", label: "claude-4.6-opus-high-thinking — Claude Opus 4.6 Thinking", tier: "frontier" },
|
|
3843
|
-
{ value: "claude-4.6-sonnet-high", label: "claude-4.6-sonnet-high — fast + smart", tier: "standard" },
|
|
3844
|
-
{ value: "gpt-5.5", label: "gpt-5.5 — OpenAI frontier", tier: "frontier" },
|
|
3845
|
-
{ value: "gpt-5.5-high-fast", label: "gpt-5.5-high-fast — GPT frontier fast", tier: "frontier" },
|
|
3846
|
-
{ value: "gpt-5.4", label: "gpt-5.4 — OpenAI strong coding", tier: "standard" },
|
|
3847
|
-
{ value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast & affordable", tier: "fast" },
|
|
3848
|
-
{ value: "gpt-5.4-nano", label: "gpt-5.4-nano — cheapest", tier: "budget" },
|
|
3849
|
-
{ value: "gpt-5.3-codex", label: "gpt-5.3-codex — OpenAI codex", tier: "standard" },
|
|
3850
|
-
{ value: "gpt-5.2", label: "gpt-5.2 — OpenAI coding", tier: "standard" },
|
|
3851
|
-
{ value: "gpt-5", label: "gpt-5 — OpenAI base", tier: "standard" },
|
|
3852
|
-
{ value: "gpt-5-fast", label: "gpt-5-fast — faster GPT-5", tier: "standard" },
|
|
3853
|
-
{ value: "gpt-5-mini", label: "gpt-5-mini — small GPT-5", tier: "fast" },
|
|
3854
|
-
{ value: "o3-pro-high", label: "o3-pro-high — advanced reasoning", tier: "frontier" },
|
|
3855
|
-
{ value: "composer-2.5", label: "composer-2.5 — Cursor latest model", tier: "standard" },
|
|
3856
|
-
{ value: "composer-2", label: "composer-2 — balanced multi-file", tier: "standard" },
|
|
3857
|
-
{ value: "composer-2-fast", label: "composer-2-fast — speed optimized", tier: "fast" },
|
|
3858
|
-
{ value: "composer-1.5", label: "composer-1.5 — legacy capable", tier: "standard" },
|
|
3859
|
-
{ value: "composer-1", label: "composer-1 — legacy model", tier: "fast" },
|
|
3860
|
-
{ value: "gemini-3.1-pro", label: "gemini-3.1-pro — Google latest", tier: "standard" },
|
|
3861
|
-
{ value: "gemini-3-pro", label: "gemini-3-pro — Google frontier", tier: "standard" },
|
|
3862
|
-
{ value: "gemini-3-flash", label: "gemini-3-flash — Google fast", tier: "fast" },
|
|
3863
|
-
{ value: "gemini-2.5-flash", label: "gemini-2.5-flash — Google budget", tier: "fast" },
|
|
3864
|
-
{ value: "grok-4.3", label: "grok-4.3 — xAI frontier", tier: "frontier" },
|
|
3865
|
-
{ value: "grok-4.20", label: "grok-4.20 — xAI standard", tier: "standard" },
|
|
3866
|
-
{ value: "kimi-k2.5", label: "kimi-k2.5 — Moonshot coding", tier: "standard" },
|
|
3867
|
-
{ value: "claude-4.5-opus", label: "claude-4.5-opus — previous gen opus", tier: "standard" },
|
|
3868
|
-
{ value: "claude-4.5-haiku", label: "claude-4.5-haiku — fast haiku", tier: "fast" },
|
|
3869
|
-
],
|
|
3870
|
-
copilot: [
|
|
3871
|
-
{ value: "auto", label: "auto — Copilot auto-selects", tier: "standard" },
|
|
3872
|
-
{ value: "claude-opus-4.7", label: "claude-opus-4.7 — Anthropic frontier", tier: "frontier" },
|
|
3873
|
-
{ value: "claude-opus-4.6", label: "claude-opus-4.6 — Anthropic frontier", tier: "frontier" },
|
|
3874
|
-
{ value: "claude-sonnet-4.6", label: "claude-sonnet-4.6 — Anthropic balanced", tier: "standard" },
|
|
3875
|
-
{ value: "claude-sonnet-4.5", label: "claude-sonnet-4.5 — Anthropic previous gen", tier: "standard" },
|
|
3876
|
-
{ value: "claude-opus-4.5", label: "claude-opus-4.5 — Anthropic legacy opus", tier: "standard" },
|
|
3877
|
-
{ value: "claude-haiku-4.5", label: "claude-haiku-4.5 — fastest", tier: "fast" },
|
|
3878
|
-
{ value: "gpt-5.5", label: "gpt-5.5 — OpenAI frontier", tier: "frontier" },
|
|
3879
|
-
{ value: "gpt-5.4", label: "gpt-5.4 — OpenAI standard", tier: "standard" },
|
|
3880
|
-
{ value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast included", tier: "fast" },
|
|
3881
|
-
{ value: "gpt-5.4-nano", label: "gpt-5.4-nano — cheapest high-volume", tier: "budget" },
|
|
3882
|
-
{ value: "gpt-5.3-codex", label: "gpt-5.3-codex — OpenAI coding", tier: "standard" },
|
|
3883
|
-
{ value: "gpt-5-mini", label: "gpt-5-mini — included fast model", tier: "fast" },
|
|
3884
|
-
{ value: "gemini-3.1-pro", label: "gemini-3.1-pro — Google latest", tier: "frontier" },
|
|
3885
|
-
{ value: "gemini-3-pro", label: "gemini-3-pro — Google frontier", tier: "standard" },
|
|
3886
|
-
{ value: "gemini-2.5-pro", label: "gemini-2.5-pro — Google frontier", tier: "standard" },
|
|
3887
|
-
{ value: "gemini-2.5-flash", label: "gemini-2.5-flash — Google fast & affordable", tier: "fast" },
|
|
3888
|
-
{ value: "gemini-3-flash", label: "gemini-3-flash — Google fast", tier: "fast" },
|
|
3889
|
-
{ value: "raptor-mini", label: "raptor-mini — preview tuned mini", tier: "fast" },
|
|
3890
|
-
{ value: "goldeneye", label: "goldeneye — preview tuned coding model", tier: "budget" },
|
|
3891
|
-
],
|
|
3892
|
-
});
|
|
3893
4261
|
// Model names are used directly — no alias resolution needed
|
|
3894
4262
|
function hasCodexApiKeyAuth(env = process.env) {
|
|
3895
4263
|
return CODEX_API_KEY_ENV_NAMES.some((name) => String(env[name] ?? "").trim().length > 0);
|
|
@@ -4342,9 +4710,14 @@ Usage:
|
|
|
4342
4710
|
prompts-gpt generate-orchestration [--title <text>] [--goal <text>] [--description <text>] [--mode <parallel|pipeline|eval>] [--overwrite] [--cwd <path>]
|
|
4343
4711
|
|
|
4344
4712
|
Why use it:
|
|
4345
|
-
Scaffolds a new orchestration
|
|
4346
|
-
|
|
4347
|
-
Run interactively (no flags) for a guided
|
|
4713
|
+
Scaffolds a new orchestration locally. Pipeline mode creates a JSON config AND step prompt files
|
|
4714
|
+
so you can immediately run the pipeline. Parallel and eval modes create Markdown prompt templates.
|
|
4715
|
+
Run interactively (no flags) for a guided wizard that lets you pick steps, agents, and models.
|
|
4716
|
+
|
|
4717
|
+
What it creates (pipeline mode):
|
|
4718
|
+
.prompts-gpt/orchestrations/<name>.json — Pipeline config with steps
|
|
4719
|
+
.prompts-gpt/orchestrations/pipeline-steps/ — Step prompt Markdown files
|
|
4720
|
+
research.md, implement.md, test-and-review.md
|
|
4348
4721
|
|
|
4349
4722
|
Options:
|
|
4350
4723
|
--title <text> Required. The orchestration title (used as filename).
|
|
@@ -4356,8 +4729,15 @@ Options:
|
|
|
4356
4729
|
--help Show this command help.
|
|
4357
4730
|
|
|
4358
4731
|
Examples:
|
|
4732
|
+
# Interactive wizard — pick steps, agents, models
|
|
4359
4733
|
prompts-gpt generate-orchestration
|
|
4734
|
+
|
|
4735
|
+
# Non-interactive pipeline scaffolding
|
|
4360
4736
|
prompts-gpt generate-orchestration --title "Full Stack Feature" --goal "Add user auth" --mode pipeline
|
|
4737
|
+
|
|
4738
|
+
# Then customize the generated step prompts and run
|
|
4739
|
+
# Edit: .prompts-gpt/orchestrations/pipeline-steps/*.md
|
|
4740
|
+
prompts-gpt orchestrate --mode pipeline --steps .prompts-gpt/orchestrations/full-stack-feature.json
|
|
4361
4741
|
`;
|
|
4362
4742
|
}
|
|
4363
4743
|
if (command === "completions") {
|
|
@@ -4485,6 +4865,8 @@ Examples:
|
|
|
4485
4865
|
|
|
4486
4866
|
Usage:
|
|
4487
4867
|
prompts-gpt sweep [-f <path>] [-n <count>] [--agent <name>] [--model <name>] [--dry-run]
|
|
4868
|
+
prompts-gpt sweep --all-sweeps [--sweep-strategy sequential|parallel] [-n <count>]
|
|
4869
|
+
prompts-gpt sweep --sweep-files <a,b,c> [--sweep-strategy sequential|parallel] [-n <count>]
|
|
4488
4870
|
|
|
4489
4871
|
Why use it:
|
|
4490
4872
|
Runs the same prompt N times, feeding each iteration's summary into the next.
|
|
@@ -4496,6 +4878,11 @@ Why use it:
|
|
|
4496
4878
|
Options:
|
|
4497
4879
|
-f, --prompt-file <path> Prompt file to sweep. Auto-detects local sweeps if omitted.
|
|
4498
4880
|
-n, --iterations <n> Number of iterations. Interactive default: 1.
|
|
4881
|
+
--all-sweeps Run every file in .prompts-gpt/sweeps.
|
|
4882
|
+
--sweep-files <list> Comma-separated sweep paths, filenames, or sweep names.
|
|
4883
|
+
--sweep-strategy <mode> File execution mode: sequential or parallel. Default: sequential.
|
|
4884
|
+
--files-mode <mode> Alias for --sweep-strategy.
|
|
4885
|
+
--file-concurrency <n> Max sweep files to run at once in parallel mode.
|
|
4499
4886
|
--agent <name> Orchestration profile. Default from config or router.
|
|
4500
4887
|
--model <name> Model override for the selected provider.
|
|
4501
4888
|
--iteration-timeout <secs> Timeout per iteration in seconds. Default: 5400 (90 min)
|
|
@@ -4510,7 +4897,7 @@ Options:
|
|
|
4510
4897
|
--max-run-dirs <n> Max artifact directories to keep. Default: 20
|
|
4511
4898
|
--summary-lines <n> Lines of summary to extract per iteration. Default: 40
|
|
4512
4899
|
--dry-run Preview what the sweep would do without executing.
|
|
4513
|
-
--parallel <n> Run iterations in parallel batches (experimental).
|
|
4900
|
+
--parallel <n> Run iterations in parallel batches inside each selected file (experimental).
|
|
4514
4901
|
--quiet, -q Suppress live tool/message logs (keep iteration headers).
|
|
4515
4902
|
--silent Suppress all output except errors and final result.
|
|
4516
4903
|
--list-models List available models for the selected provider.
|
|
@@ -4534,6 +4921,11 @@ Examples:
|
|
|
4534
4921
|
prompts-gpt sweep # interactive picker
|
|
4535
4922
|
prompts-gpt sweep -f .prompts-gpt/sweeps/sdk-hardening.md # explicit file
|
|
4536
4923
|
prompts-gpt sweep -f .prompts-gpt/sweeps/design.md -n 5 # 5 iterations
|
|
4924
|
+
prompts-gpt sweep --all-sweeps --sweep-strategy sequential # every sweep file, one after another
|
|
4925
|
+
prompts-gpt sweep --all-sweeps --sweep-strategy parallel -n 2 # every sweep file in parallel, 2 iterations each
|
|
4926
|
+
prompts-gpt sweep --sweep-files design,research --sweep-strategy parallel -n 2
|
|
4927
|
+
prompts-gpt sweep --sweep-files .prompts-gpt/sweeps/a.md,.prompts-gpt/sweeps/b.md --sweep-strategy sequential
|
|
4928
|
+
prompts-gpt sweep -f .prompts-gpt/sweeps/design.md -n 6 --parallel 3
|
|
4537
4929
|
prompts-gpt sweep --model claude-sonnet-4-6 # specify model
|
|
4538
4930
|
prompts-gpt sweep --quiet # suppress live logs
|
|
4539
4931
|
prompts-gpt sweep --list-models --agent codex # see codex models
|
|
@@ -4677,7 +5069,7 @@ Options:
|
|
|
4677
5069
|
return `prompts-gpt orchestrate
|
|
4678
5070
|
|
|
4679
5071
|
Usage:
|
|
4680
|
-
prompts-gpt orchestrate --mode <parallel|pipeline|eval> [--prompt-file <path>] [--providers <list>] [--agent <name>] [--model <name>] [--timeout <secs>] [--evaluator <name>] [--evaluator-model <name>] [--criteria <list>] [--eval-criteria <list>] [--steps <file>] [--dry-run] [--json] [--cwd <path>]
|
|
5072
|
+
prompts-gpt orchestrate --mode <parallel|pipeline|eval> [--prompt-file <path>] [--providers <list>] [--agent <name>] [--model <name>] [--timeout <secs>] [--evaluator <name>] [--evaluator-model <name>] [--criteria <list>] [--eval-criteria <list>] [--steps <file>] [--continue-on-error] [--dry-run] [--json] [--cwd <path>]
|
|
4681
5073
|
|
|
4682
5074
|
Why use it:
|
|
4683
5075
|
Run multi-agent orchestration — execute the same prompt across multiple providers in parallel,
|
|
@@ -4700,6 +5092,7 @@ Options:
|
|
|
4700
5092
|
--eval-criteria <list> Alias for --criteria in eval mode and sweep self-evaluation.
|
|
4701
5093
|
--steps <file> JSON step file for pipeline mode.
|
|
4702
5094
|
--timeout <secs> Timeout per provider in seconds.
|
|
5095
|
+
--continue-on-error Continue running remaining pipeline steps even if one fails.
|
|
4703
5096
|
--dry-run Preview what the orchestration would do.
|
|
4704
5097
|
--list-models Show model choices for the resolved provider(s).
|
|
4705
5098
|
--non-interactive Skip all interactive prompts.
|
|
@@ -4825,8 +5218,8 @@ function slugifyFilename(text, fallback) {
|
|
|
4825
5218
|
}
|
|
4826
5219
|
function buildProgressBar(completed, total, width) {
|
|
4827
5220
|
const safeWidth = Number.isFinite(width) && width > 0 ? Math.trunc(width) : 20;
|
|
4828
|
-
const safeTotal = Math.
|
|
4829
|
-
const safeCompleted = Math.max(0, Math.min(completed, safeTotal));
|
|
5221
|
+
const safeTotal = Number.isFinite(total) && total > 0 ? Math.trunc(total) : 1;
|
|
5222
|
+
const safeCompleted = Number.isFinite(completed) ? Math.max(0, Math.min(Math.trunc(completed), safeTotal)) : 0;
|
|
4830
5223
|
const fraction = safeCompleted / safeTotal;
|
|
4831
5224
|
const filled = Math.min(Math.floor(fraction * safeWidth), safeWidth);
|
|
4832
5225
|
const empty = safeWidth - filled;
|
|
@@ -4912,9 +5305,12 @@ function interactiveSelect(prompt, options) {
|
|
|
4912
5305
|
const maxVisible = Math.min(options.length, Math.max(3, termRows - 4));
|
|
4913
5306
|
const useUnicode = supportsUnicode();
|
|
4914
5307
|
const pointer = useUnicode ? "\u276f" : ">";
|
|
4915
|
-
const
|
|
4916
|
-
|
|
4917
|
-
|
|
5308
|
+
const getScrollStart = (visible) => visible.length <= maxVisible
|
|
5309
|
+
? 0
|
|
5310
|
+
: Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), visible.length - maxVisible));
|
|
5311
|
+
const formatLine = (entry, shortcutIndex, selected) => {
|
|
5312
|
+
const numberWidth = String(Math.min(9, maxVisible)).length;
|
|
5313
|
+
const num = shortcutIndex <= 9 ? String(shortcutIndex).padStart(numberWidth, " ") : " ".repeat(numberWidth);
|
|
4918
5314
|
const prefix = selected ? colorize(pointer, "\x1b[36m") : " ";
|
|
4919
5315
|
const label = selected ? colorize(entry.label, "\x1b[1m") : entry.label;
|
|
4920
5316
|
return ` ${prefix} ${num}. ${label}`;
|
|
@@ -4935,15 +5331,13 @@ function interactiveSelect(prompt, options) {
|
|
|
4935
5331
|
const visibleCount = Math.min(maxVisible, visible.length);
|
|
4936
5332
|
const posLabel = visible.length > 1 ? ` (${cursor + 1}/${visible.length})` : "";
|
|
4937
5333
|
const filterLabel = filterText ? colorize(` filter: "${filterText}"`, "\x1b[33m") : "";
|
|
4938
|
-
const scrollStart = visible
|
|
4939
|
-
? 0
|
|
4940
|
-
: Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), visible.length - maxVisible));
|
|
5334
|
+
const scrollStart = getScrollStart(visible);
|
|
4941
5335
|
const lines = [];
|
|
4942
5336
|
lines.push(`${prompt}${posLabel}${filterLabel}`);
|
|
4943
5337
|
for (let vi = 0; vi < visibleCount; vi++) {
|
|
4944
5338
|
const i = scrollStart + vi;
|
|
4945
5339
|
if (i < visible.length) {
|
|
4946
|
-
lines.push(formatLine(visible[i],
|
|
5340
|
+
lines.push(formatLine(visible[i], vi + 1, i === cursor));
|
|
4947
5341
|
}
|
|
4948
5342
|
}
|
|
4949
5343
|
if (visible.length > maxVisible) {
|
|
@@ -5000,7 +5394,7 @@ function interactiveSelect(prompt, options) {
|
|
|
5000
5394
|
return;
|
|
5001
5395
|
}
|
|
5002
5396
|
stdin.resume();
|
|
5003
|
-
const
|
|
5397
|
+
const cancelSelection = () => {
|
|
5004
5398
|
if (settled)
|
|
5005
5399
|
return;
|
|
5006
5400
|
settled = true;
|
|
@@ -5011,11 +5405,17 @@ function interactiveSelect(prompt, options) {
|
|
|
5011
5405
|
catch { /* stdout may be closed */ }
|
|
5012
5406
|
reject(new CliError("Selection cancelled.", CLI_EXIT_CODES.general));
|
|
5013
5407
|
};
|
|
5408
|
+
const onSigint = () => { cancelSelection(); };
|
|
5409
|
+
const onSigterm = () => { cancelSelection(); };
|
|
5014
5410
|
const cleanup = () => {
|
|
5015
5411
|
try {
|
|
5016
5412
|
process.removeListener("SIGINT", onSigint);
|
|
5017
5413
|
}
|
|
5018
5414
|
catch { /* ignore */ }
|
|
5415
|
+
try {
|
|
5416
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
5417
|
+
}
|
|
5418
|
+
catch { /* ignore */ }
|
|
5019
5419
|
try {
|
|
5020
5420
|
stdin.removeListener("data", onData);
|
|
5021
5421
|
}
|
|
@@ -5047,6 +5447,7 @@ function interactiveSelect(prompt, options) {
|
|
|
5047
5447
|
stdin.on("end", onEnd);
|
|
5048
5448
|
stdin.on("error", onError);
|
|
5049
5449
|
process.on("SIGINT", onSigint);
|
|
5450
|
+
process.on("SIGTERM", onSigterm);
|
|
5050
5451
|
const onData = (data) => {
|
|
5051
5452
|
const visible = getVisibleOptions();
|
|
5052
5453
|
for (let ci = 0; ci < data.length; ci++) {
|
|
@@ -5182,17 +5583,18 @@ function interactiveSelect(prompt, options) {
|
|
|
5182
5583
|
}
|
|
5183
5584
|
else if (ch >= "1" && ch <= "9" && !filterText) {
|
|
5184
5585
|
const idx = parseInt(ch, 10) - 1;
|
|
5185
|
-
|
|
5586
|
+
const visibleIndex = getScrollStart(visible) + idx;
|
|
5587
|
+
if (visibleIndex < visible.length && idx < 9) {
|
|
5186
5588
|
if (settled)
|
|
5187
5589
|
return;
|
|
5188
5590
|
settled = true;
|
|
5189
5591
|
cleanup();
|
|
5190
5592
|
stdout.write("\n");
|
|
5191
|
-
resolve(visible[
|
|
5593
|
+
resolve(visible[visibleIndex].value);
|
|
5192
5594
|
return;
|
|
5193
5595
|
}
|
|
5194
5596
|
}
|
|
5195
|
-
else if (ch
|
|
5597
|
+
else if (isPrintableInteractiveInput(ch)) {
|
|
5196
5598
|
filterText += ch;
|
|
5197
5599
|
cursor = 0;
|
|
5198
5600
|
render();
|
|
@@ -5202,6 +5604,11 @@ function interactiveSelect(prompt, options) {
|
|
|
5202
5604
|
stdin.on("data", onData);
|
|
5203
5605
|
});
|
|
5204
5606
|
}
|
|
5607
|
+
function isPrintableInteractiveInput(value) {
|
|
5608
|
+
if (!value)
|
|
5609
|
+
return false;
|
|
5610
|
+
return !/[\x00-\x1f\x7f]/.test(value);
|
|
5611
|
+
}
|
|
5205
5612
|
async function interactiveInput(prompt, defaultValue) {
|
|
5206
5613
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
5207
5614
|
throw new CliError("Interactive input requires a TTY.", CLI_EXIT_CODES.usage);
|
|
@@ -5225,6 +5632,14 @@ async function interactiveInput(prompt, defaultValue) {
|
|
|
5225
5632
|
resolved = true;
|
|
5226
5633
|
resolve(defaultValue || "");
|
|
5227
5634
|
});
|
|
5635
|
+
rl.on("error", (err) => {
|
|
5636
|
+
if (resolved)
|
|
5637
|
+
return;
|
|
5638
|
+
resolved = true;
|
|
5639
|
+
rl.close();
|
|
5640
|
+
rl.removeAllListeners();
|
|
5641
|
+
reject(new CliError(`Input stream error: ${err.message}`, CLI_EXIT_CODES.general));
|
|
5642
|
+
});
|
|
5228
5643
|
rl.on("SIGINT", () => {
|
|
5229
5644
|
if (resolved)
|
|
5230
5645
|
return;
|
|
@@ -5236,14 +5651,26 @@ async function interactiveInput(prompt, defaultValue) {
|
|
|
5236
5651
|
});
|
|
5237
5652
|
}
|
|
5238
5653
|
async function checkPromptsGptSiteReachable(apiUrl) {
|
|
5654
|
+
if (typeof globalThis.fetch !== "function") {
|
|
5655
|
+
return { ok: false, status: null };
|
|
5656
|
+
}
|
|
5239
5657
|
const target = new URL("/", apiUrl).toString();
|
|
5240
|
-
const request = (method) =>
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5658
|
+
const request = async (method) => {
|
|
5659
|
+
let timer = null;
|
|
5660
|
+
try {
|
|
5661
|
+
return await Promise.race([
|
|
5662
|
+
globalThis.fetch(target, { method }),
|
|
5663
|
+
new Promise((_, reject) => {
|
|
5664
|
+
timer = setTimeout(() => reject(new Error("timeout")), 5000);
|
|
5665
|
+
timer.unref?.();
|
|
5666
|
+
}),
|
|
5667
|
+
]);
|
|
5668
|
+
}
|
|
5669
|
+
finally {
|
|
5670
|
+
if (timer)
|
|
5671
|
+
clearTimeout(timer);
|
|
5672
|
+
}
|
|
5673
|
+
};
|
|
5247
5674
|
let response = await request("HEAD");
|
|
5248
5675
|
if (response.status === 405) {
|
|
5249
5676
|
response = await request("GET");
|
|
@@ -5284,6 +5711,48 @@ async function readSweepFrontmatter(filePath) {
|
|
|
5284
5711
|
async function readSweepIterationsFromFrontmatter(filePath) {
|
|
5285
5712
|
return (await readSweepFrontmatter(filePath)).iterations;
|
|
5286
5713
|
}
|
|
5714
|
+
async function resolveSweepFileSelection(cwd, flags) {
|
|
5715
|
+
const allSweeps = Boolean(flags["all-sweeps"]);
|
|
5716
|
+
const rawSelected = getStringFlag(flags, "sweep-files");
|
|
5717
|
+
if (!allSweeps && !rawSelected)
|
|
5718
|
+
return [];
|
|
5719
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
5720
|
+
if (assets.sweeps.length === 0) {
|
|
5721
|
+
throw new CliError("No sweep files found in .prompts-gpt/sweeps.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
5722
|
+
}
|
|
5723
|
+
if (allSweeps) {
|
|
5724
|
+
return assets.sweeps.map((sweep) => sweep.file);
|
|
5725
|
+
}
|
|
5726
|
+
const requested = rawSelected
|
|
5727
|
+
.split(",")
|
|
5728
|
+
.map((item) => item.trim())
|
|
5729
|
+
.filter(Boolean);
|
|
5730
|
+
if (requested.length === 0) {
|
|
5731
|
+
throw new CliError("--sweep-files must include at least one file path, filename, or sweep name.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
5732
|
+
}
|
|
5733
|
+
const resolved = [];
|
|
5734
|
+
const seen = new Set();
|
|
5735
|
+
for (const item of requested) {
|
|
5736
|
+
const directPath = path.resolve(cwd, item);
|
|
5737
|
+
let matched = existsSync(directPath) ? item : undefined;
|
|
5738
|
+
if (!matched) {
|
|
5739
|
+
const normalizedItem = item.replace(/\.md$/i, "");
|
|
5740
|
+
const asset = assets.sweeps.find((sweep) => {
|
|
5741
|
+
const basename = path.basename(sweep.file, ".md");
|
|
5742
|
+
return sweep.file === item || basename === item || basename === normalizedItem || sweep.name === item || sweep.name === normalizedItem;
|
|
5743
|
+
});
|
|
5744
|
+
matched = asset?.file;
|
|
5745
|
+
}
|
|
5746
|
+
if (!matched || !existsSync(path.resolve(cwd, matched))) {
|
|
5747
|
+
throw new CliError(`Sweep file not found: ${item}\n\nUse --sweep-files with paths, filenames, or names from \`prompts-gpt list\`.`, CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
|
|
5748
|
+
}
|
|
5749
|
+
if (!seen.has(matched)) {
|
|
5750
|
+
seen.add(matched);
|
|
5751
|
+
resolved.push(matched);
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
return resolved;
|
|
5755
|
+
}
|
|
5287
5756
|
function buildGenerateInput(flags) {
|
|
5288
5757
|
return {
|
|
5289
5758
|
goal: getStringFlag(flags, "goal") || "",
|
|
@@ -5347,7 +5816,7 @@ function printCompletionScript(shell) {
|
|
|
5347
5816
|
const commands = [...COMMANDS].sort().join(" ");
|
|
5348
5817
|
const sweepFlags = "--prompt-file --agent --model --iterations --timeout --retries --json --verbose --dry-run --non-interactive --cwd --help --quiet --list-models";
|
|
5349
5818
|
const runFlags = "--prompt-file --agent --model --timeout --verbose --dry-run --json --non-interactive --cwd --help --list-models";
|
|
5350
|
-
const orchFlags = "--mode --prompt-file --providers --agent --model --timeout --evaluator --evaluator-model --criteria --eval-criteria --steps --dry-run --json --non-interactive --cwd --help --list-models";
|
|
5819
|
+
const orchFlags = "--mode --prompt-file --providers --agent --model --timeout --evaluator --evaluator-model --criteria --eval-criteria --steps --continue-on-error --dry-run --json --non-interactive --cwd --help --list-models";
|
|
5351
5820
|
if (shell === "zsh") {
|
|
5352
5821
|
console.log(`# Zsh completions for prompts-gpt
|
|
5353
5822
|
# Add to ~/.zshrc: eval "$(prompts-gpt completions zsh)"
|
|
@@ -5455,19 +5924,29 @@ mode: "parallel"
|
|
|
5455
5924
|
- Quality: Is the output production-ready?
|
|
5456
5925
|
- Correctness: Are there any bugs or issues?
|
|
5457
5926
|
`;
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5927
|
+
function buildPipelineStepPrompt(stepName, description, stepIndex, totalSteps) {
|
|
5928
|
+
const isFirstStep = stepIndex === 0;
|
|
5929
|
+
const isLastStep = stepIndex === totalSteps - 1;
|
|
5930
|
+
const sections = [
|
|
5931
|
+
`You are the **{{ step_name }}** step ({{ step_index }}/{{ total_steps }}) of a multi-step pipeline.`,
|
|
5932
|
+
"",
|
|
5933
|
+
`## Goal`,
|
|
5934
|
+
"",
|
|
5935
|
+
description,
|
|
5936
|
+
"",
|
|
5937
|
+
];
|
|
5938
|
+
if (isFirstStep) {
|
|
5939
|
+
sections.push("## Instructions", "", "1. Analyze the codebase structure and understand the existing patterns", "2. Identify the best approach for the task", "3. Produce a structured plan with specific file paths and changes", "", "## Output Format", "", "Provide a structured plan with:", "- Architecture decisions", "- Implementation checklist (numbered, with file paths)", "- Risk assessment", "- Existing patterns to follow", "");
|
|
5940
|
+
}
|
|
5941
|
+
else if (isLastStep) {
|
|
5942
|
+
sections.push("## Instructions", "", "1. Review the output from the previous step(s)", "2. Validate correctness and completeness", "3. Run any available tests: `npm run lint && npm run build`", "4. Fix any issues found", "", "## Output Format", "", "Provide:", "- Files reviewed (pass/fail per file)", "- Tests run and results", "- Issues found and fixed", "- Final status", "");
|
|
5943
|
+
}
|
|
5944
|
+
else {
|
|
5945
|
+
sections.push("## Instructions", "", "1. Read the plan/context from the previous step", "2. Implement each item systematically", "3. Follow existing patterns identified in the research", "4. Handle error states, edge cases, and validation", "", "## Output Format", "", "List every file created or modified with a one-line description.", "");
|
|
5946
|
+
}
|
|
5947
|
+
sections.push("{{ previous_output }}", "");
|
|
5948
|
+
return sections.join("\n");
|
|
5949
|
+
}
|
|
5471
5950
|
main().catch((error) => {
|
|
5472
5951
|
if (error instanceof CliError) {
|
|
5473
5952
|
try {
|