@promptbook/cli 0.114.0-4 → 0.114.0-5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +3 -3
  2. package/esm/index.es.js +268 -74
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/scripts/run-codex-prompts/common/createCoderRunStepTracker.d.ts +48 -0
  5. package/esm/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +43 -0
  6. package/esm/scripts/run-codex-prompts/prompts/formatCoderRunSteps.d.ts +6 -3
  7. package/esm/scripts/run-codex-prompts/prompts/isPromptSectionUnfinished.d.ts +8 -0
  8. package/esm/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +1 -1
  9. package/esm/scripts/run-codex-prompts/prompts/markPromptInProgress.d.ts +27 -0
  10. package/esm/scripts/run-codex-prompts/prompts/resolvePromptStatusLine.d.ts +19 -0
  11. package/esm/scripts/run-codex-prompts/prompts/writePromptStatusLine.d.ts +9 -0
  12. package/esm/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +1 -1
  13. package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
  14. package/esm/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
  15. package/esm/scripts/run-codex-prompts/testing/runPromptWithTestFeedback.d.ts +6 -0
  16. package/esm/scripts/verify-prompts/$orderPromptFiles.d.ts +9 -0
  17. package/esm/scripts/verify-prompts/VerifyPromptsOrder.d.ts +26 -0
  18. package/esm/scripts/verify-prompts/verify-prompts.d.ts +3 -2
  19. package/esm/src/cli/cli-commands/coder/verify.d.ts +1 -1
  20. package/esm/src/version.d.ts +1 -1
  21. package/package.json +1 -1
  22. package/src/cli/cli-commands/coder/verify.ts +29 -5
  23. package/src/other/templates/getTemplatesPipelineCollection.ts +704 -843
  24. package/src/version.ts +2 -2
  25. package/src/versions.txt +1 -0
  26. package/umd/index.umd.js +268 -74
  27. package/umd/index.umd.js.map +1 -1
  28. package/umd/scripts/run-codex-prompts/common/createCoderRunStepTracker.d.ts +48 -0
  29. package/umd/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +43 -0
  30. package/umd/scripts/run-codex-prompts/prompts/formatCoderRunSteps.d.ts +6 -3
  31. package/umd/scripts/run-codex-prompts/prompts/isPromptSectionUnfinished.d.ts +8 -0
  32. package/umd/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +1 -1
  33. package/umd/scripts/run-codex-prompts/prompts/markPromptInProgress.d.ts +27 -0
  34. package/umd/scripts/run-codex-prompts/prompts/resolvePromptStatusLine.d.ts +19 -0
  35. package/umd/scripts/run-codex-prompts/prompts/writePromptStatusLine.d.ts +9 -0
  36. package/umd/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +1 -1
  37. package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
  38. package/umd/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
  39. package/umd/scripts/run-codex-prompts/testing/runPromptWithTestFeedback.d.ts +6 -0
  40. package/umd/scripts/verify-prompts/$orderPromptFiles.d.ts +9 -0
  41. package/umd/scripts/verify-prompts/VerifyPromptsOrder.d.ts +26 -0
  42. package/umd/scripts/verify-prompts/verify-prompts.d.ts +3 -2
  43. package/umd/src/cli/cli-commands/coder/verify.d.ts +1 -1
  44. package/umd/src/version.d.ts +1 -1
  45. package/esm/scripts/run-codex-prompts/prompts/replacePromptTodoStatusLine.d.ts +0 -7
  46. package/umd/scripts/run-codex-prompts/prompts/replacePromptTodoStatusLine.d.ts +0 -7
package/README.md CHANGED
@@ -526,7 +526,7 @@ Or you can install them separately:
526
526
 
527
527
  Promptbook Coder is **not another standalone coding model**. It is an orchestration layer over coding agents such as **GitHub Copilot**, **OpenAI Codex**, **Claude Code**, **Opencode**, **Cline**, and **Gemini CLI**. The difference is that Promptbook Coder adds a repeatable repository workflow on top of them:
528
528
 
529
- - prompt files with explicit statuses like `[ ]`, `[x]`, and `[-]`
529
+ - prompt files with explicit statuses like `[ ]`, `[^]`, `[x]`, and `[-]`
530
530
  - automatic selection of the next runnable task, including priority support
531
531
  - optional shared repo context loaded from a file such as `AGENTS.md`
532
532
  - automatic `git add`, commit, and push after each successful prompt
@@ -541,11 +541,11 @@ In short: tools like Claude Code, Codex, or GitHub Copilot are the **engines**;
541
541
  1. `ptbk coder init` prepares the project for the coder workflow, seeds project-owned generic templates in `prompts/templates/`, creates a starter `AGENTS.md` context file, adds helper `npm run coder:*` scripts, ensures `.gitignore` ignores `/.promptbook`, and configures VS Code prompt screenshots in `prompts/screenshots/`.
542
542
  2. `ptbk coder generate-boilerplates` creates prompt files in `prompts/`.
543
543
  3. You replace placeholder `@@@` sections with real coding tasks.
544
- 4. `ptbk coder run` sends the next ready `[ ]` prompt to the selected coding agent.
544
+ 4. `ptbk coder run` sends the next ready `[ ]` prompt to the selected coding agent and immediately marks it as in progress `[^]`, naming the harness, the model and the step which is running.
545
545
  5. Promptbook Coder marks the prompt as done `[x]`, records runner metadata, then stages, commits, and pushes the resulting changes.
546
546
  6. `ptbk coder verify` reviews completed prompts, archives finished files to `prompts/done/`, and appends a repair prompt when more work is needed.
547
547
 
548
- Prompts marked with `[-]` are not ready yet, prompts containing `@@@` are treated as not fully written, and prompts with more `!` markers have higher priority.
548
+ Prompts marked with `[-]` are not ready yet, prompts containing `@@@` are treated as not fully written, and prompts with more `!` markers have higher priority. A prompt left as `[^]` was interrupted in the middle of its implementation — the in-progress status is never reverted, so a killed or crashed coder always leaves that signal behind.
549
549
 
550
550
  #### Features
551
551
 
package/esm/index.es.js CHANGED
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-4';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-5';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -29748,6 +29748,32 @@ function appendCoderContext(prompt, context) {
29748
29748
  `);
29749
29749
  }
29750
29750
 
29751
+ /**
29752
+ * Creates the step tracker of one prompt round.
29753
+ *
29754
+ * The tracker is the single place which knows which steps have finished and which one is running,
29755
+ * so both the finished `[x]` status line and the intermediate `[^]` in-progress status lines are
29756
+ * built from the very same data.
29757
+ */
29758
+ function createCoderRunStepTracker(onStepStarted) {
29759
+ const steps = [];
29760
+ let reportedLoginMethod;
29761
+ return {
29762
+ steps,
29763
+ async startStep(kind) {
29764
+ await (onStepStarted === null || onStepStarted === void 0 ? void 0 : onStepStarted({
29765
+ startedStepKind: kind,
29766
+ finishedSteps: [...steps],
29767
+ loginMethod: reportedLoginMethod,
29768
+ }));
29769
+ },
29770
+ finishStep(step, loginMethod) {
29771
+ steps.push(step);
29772
+ reportedLoginMethod = loginMethod !== null && loginMethod !== void 0 ? loginMethod : reportedLoginMethod;
29773
+ },
29774
+ };
29775
+ }
29776
+
29751
29777
  /**
29752
29778
  * Maximum amount of test output embedded into an agent prompt.
29753
29779
  */
@@ -29793,7 +29819,7 @@ const SHELL_SCRIPT_EXTENSION = '.sh';
29793
29819
  async function runPromptWithTestFeedback(options) {
29794
29820
  var _a, _b, _c, _d;
29795
29821
  const normalizedTestCommand = (_a = options.testCommand) === null || _a === void 0 ? void 0 : _a.trim();
29796
- const steps = [];
29822
+ const stepTracker = createCoderRunStepTracker(options.onStepStarted);
29797
29823
  if (!normalizedTestCommand) {
29798
29824
  (_b = options.onAttemptStarted) === null || _b === void 0 ? void 0 : _b.call(options, 1);
29799
29825
  await waitForPromptAttemptPauseCheckpoint(options.waitForPauseCheckpoint, options.runner.name, 1);
@@ -29801,9 +29827,9 @@ async function runPromptWithTestFeedback(options) {
29801
29827
  runOptions: options,
29802
29828
  prompt: options.prompt,
29803
29829
  kind: 'implementation',
29804
- steps,
29830
+ stepTracker,
29805
29831
  });
29806
- return { ...result, attemptCount: 1, steps };
29832
+ return { ...result, attemptCount: 1, steps: stepTracker.steps };
29807
29833
  }
29808
29834
  const runPromptTestCommandExecutor = (_c = options.runPromptTestCommandExecutor) !== null && _c !== void 0 ? _c : runPromptTestCommand;
29809
29835
  let promptForCurrentAttempt = options.prompt;
@@ -29814,7 +29840,7 @@ async function runPromptWithTestFeedback(options) {
29814
29840
  runOptions: options,
29815
29841
  prompt: promptForCurrentAttempt,
29816
29842
  kind: attemptCount === 1 ? 'implementation' : 'fixing',
29817
- steps,
29843
+ stepTracker,
29818
29844
  });
29819
29845
  await waitForVerificationPauseCheckpoint(options.waitForPauseCheckpoint, normalizedTestCommand, attemptCount);
29820
29846
  console.info(colors.gray(`Running verification command after attempt #${attemptCount}: ${normalizedTestCommand}`));
@@ -29822,10 +29848,10 @@ async function runPromptWithTestFeedback(options) {
29822
29848
  runPromptTestCommandExecutor,
29823
29849
  testCommand: normalizedTestCommand,
29824
29850
  runOptions: options,
29825
- steps,
29851
+ stepTracker,
29826
29852
  });
29827
29853
  if (failedVerification === undefined) {
29828
- return { ...result, attemptCount, steps };
29854
+ return { ...result, attemptCount, steps: stepTracker.steps };
29829
29855
  }
29830
29856
  const fullVerificationOutput = formatUnknownErrorDetails(failedVerification.error);
29831
29857
  const feedbackVerificationOutput = limitTestOutput(fullVerificationOutput);
@@ -29851,7 +29877,8 @@ async function runPromptWithTestFeedback(options) {
29851
29877
  * Runs one coding attempt through the runner, timing it and recording it as an implementation or fixing step.
29852
29878
  */
29853
29879
  async function runRunnerPromptStep(options) {
29854
- const { runOptions, prompt, kind, steps } = options;
29880
+ const { runOptions, prompt, kind, stepTracker } = options;
29881
+ await stepTracker.startStep(kind);
29855
29882
  const stepStartedTimeMs = Date.now();
29856
29883
  const result = await runOptions.runner.runPrompt({
29857
29884
  prompt,
@@ -29861,7 +29888,7 @@ async function runRunnerPromptStep(options) {
29861
29888
  preserveArtifactsOnSuccess: runOptions.preserveArtifactsOnSuccess,
29862
29889
  waitForPauseCheckpoint: runOptions.waitForPauseCheckpoint,
29863
29890
  });
29864
- steps.push({ kind, usage: result.usage, durationMs: Date.now() - stepStartedTimeMs });
29891
+ stepTracker.finishStep({ kind, usage: result.usage, durationMs: Date.now() - stepStartedTimeMs }, result.loginMethod);
29865
29892
  return result;
29866
29893
  }
29867
29894
  /**
@@ -29869,7 +29896,8 @@ async function runRunnerPromptStep(options) {
29869
29896
  * returns the failure (or `undefined` when the verification passed).
29870
29897
  */
29871
29898
  async function runVerificationStep(options) {
29872
- const { runPromptTestCommandExecutor, testCommand, runOptions, steps } = options;
29899
+ const { runPromptTestCommandExecutor, testCommand, runOptions, stepTracker } = options;
29900
+ await stepTracker.startStep('testing');
29873
29901
  const stepStartedTimeMs = Date.now();
29874
29902
  try {
29875
29903
  await runPromptTestCommandExecutor({
@@ -29885,7 +29913,7 @@ async function runVerificationStep(options) {
29885
29913
  return { error };
29886
29914
  }
29887
29915
  finally {
29888
- steps.push({ kind: 'testing', usage: null, durationMs: Date.now() - stepStartedTimeMs });
29916
+ stepTracker.finishStep({ kind: 'testing', usage: null, durationMs: Date.now() - stepStartedTimeMs });
29889
29917
  }
29890
29918
  }
29891
29919
  /**
@@ -41172,6 +41200,45 @@ function normalizeCommandOptionValue(value) {
41172
41200
  // Note: [🟡] Code for CLI command [server](src/cli/cli-commands/coder/server.ts) should never be published outside of `@promptbook/cli`
41173
41201
  // Note: [💞] Ignore a discrepancy between file name and entity name
41174
41202
 
41203
+ /**
41204
+ * Orders supported by `ptbk coder verify --order`.
41205
+ */
41206
+ const VERIFY_PROMPTS_ORDER_VALUES = ['from-earliest', 'from-latest', 'random'];
41207
+ /**
41208
+ * Order used when `--order` is not provided.
41209
+ */
41210
+ const DEFAULT_VERIFY_PROMPTS_ORDER = 'from-earliest';
41211
+ /**
41212
+ * Human-readable description of each supported order, shared by the CLI help and the verification output.
41213
+ */
41214
+ const VERIFY_PROMPTS_ORDER_DESCRIPTIONS = {
41215
+ 'from-earliest': 'from the earliest prompt file',
41216
+ 'from-latest': 'from the latest prompt file',
41217
+ random: 'in random order',
41218
+ };
41219
+ /**
41220
+ * Parses and validates one raw `--order` value.
41221
+ *
41222
+ * Note: `ptbk coder verify` lets Commander validate the value, this is used by the standalone script which parses the raw arguments itself
41223
+ */
41224
+ function parseVerifyPromptsOrder(orderValue) {
41225
+ if (orderValue === undefined) {
41226
+ return DEFAULT_VERIFY_PROMPTS_ORDER;
41227
+ }
41228
+ if (VERIFY_PROMPTS_ORDER_VALUES.includes(orderValue)) {
41229
+ return orderValue;
41230
+ }
41231
+ throw new NotAllowed(spaceTrim$1((block) => `
41232
+ Invalid value for \`--order\`: \`${orderValue}\`.
41233
+
41234
+ Use one of the supported orders:
41235
+ ${block(VERIFY_PROMPTS_ORDER_VALUES.map((value) => `- \`${value}\` processes the prompt files ${VERIFY_PROMPTS_ORDER_DESCRIPTIONS[value]}`).join('\n'))}
41236
+
41237
+ Actionable hint:
41238
+ - The removed \`--reverse\` flag is now \`--order from-latest\`.
41239
+ `));
41240
+ }
41241
+
41175
41242
  /**
41176
41243
  * Initializes `coder verify` command for Promptbook CLI utilities
41177
41244
  *
@@ -41191,21 +41258,27 @@ function $initializeCoderVerifyCommand(program) {
41191
41258
  - Auto-appends repair prompts for incomplete work
41192
41259
  - Processes files with all-done prompts first
41193
41260
  - Supports ignoring matching prompt candidates for one verification run
41261
+ - Supports processing the prompt files from the earliest, from the latest or in random order
41194
41262
 
41195
41263
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
41196
41264
 
41197
41265
  Note: The git synchronization is applied around each single verification, not once per run.
41198
41266
  `));
41199
- command.option('--reverse', 'Process prompt files in reverse order', false);
41267
+ command.addOption(new Option('--order <order>', spaceTrim$1((block) => `
41268
+ Order in which the prompt files are processed:
41269
+ ${block(VERIFY_PROMPTS_ORDER_VALUES.map((order) => `- ${order}: ${VERIFY_PROMPTS_ORDER_DESCRIPTIONS[order]}`).join('\n'))}
41270
+ `))
41271
+ .choices([...VERIFY_PROMPTS_ORDER_VALUES])
41272
+ .default(DEFAULT_VERIFY_PROMPTS_ORDER));
41200
41273
  command.option('--ignore <candidate-text>', 'Ignore prompt files whose filename or first prompt line contains the given text (repeatable)', collectStringOption, []);
41201
41274
  addCoderGitSyncOptions(command);
41202
41275
  command.action(handleActionErrors(async (cliOptions) => {
41203
- const { reverse, ignore } = cliOptions;
41276
+ const { order, ignore } = cliOptions;
41204
41277
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
41205
41278
  // Note: Import the main function dynamically to avoid loading heavy dependencies until needed
41206
41279
  const { verifyPrompts } = await Promise.resolve().then(function () { return verifyPrompts$1; });
41207
41280
  try {
41208
- await verifyPrompts({ reverse, ignore, gitSync });
41281
+ await verifyPrompts({ order, ignore, gitSync });
41209
41282
  }
41210
41283
  catch (error) {
41211
41284
  console.error(colors.bgRed('Prompt verification failed:'), error);
@@ -70350,7 +70423,7 @@ function parsePromptFile(filePath, content) {
70350
70423
  }
70351
70424
  /**
70352
70425
  * Parses a status line like "[ ] !!", "[ ] use `gpt` !!!!!" or "[-]" into status and priority.
70353
- * For [x] done and [!] failed prompts, allow metadata after the status marker.
70426
+ * For [x] done, [!] failed and [^] in-progress prompts, allow metadata after the status marker.
70354
70427
  */
70355
70428
  function parseStatusLine(line) {
70356
70429
  var _a, _b, _c, _d;
@@ -70364,6 +70437,11 @@ function parseStatusLine(line) {
70364
70437
  if (failedMatch) {
70365
70438
  return { status: 'failed', priority: 0 };
70366
70439
  }
70440
+ // For in-progress prompts [^], allow any content after (for the steps recorded so far)
70441
+ const inProgressMatch = line.match(/^\[(?<status>\^)\]/);
70442
+ if (inProgressMatch) {
70443
+ return { status: 'in-progress', priority: 0 };
70444
+ }
70367
70445
  // For not-ready [-], keep the historical clean-line syntax.
70368
70446
  if (/^\[-\]\s*!*\s*$/u.test(line)) {
70369
70447
  return { status: 'not-ready', priority: 0 };
@@ -72943,14 +73021,25 @@ const CODER_RUN_STEP_LABELS = {
72943
73021
  fixing: 'Fixing',
72944
73022
  };
72945
73023
  /**
72946
- * Formats the per-step usage breakdown recorded for one finished prompt.
73024
+ * Suffix marking the one step which has already started but has neither a price nor a duration yet.
73025
+ */
73026
+ const IN_PROGRESS_STEP_SUFFIX = 'in progress';
73027
+ /**
73028
+ * Formats the per-step usage breakdown recorded for one prompt.
72947
73029
  *
72948
73030
  * Produces a `; `-separated summary such as
72949
73031
  * `Implementation $8.01 6 hours; Testing 1 hour; Fixing $3.14 2 hours` where each coding step carries its
72950
73032
  * price and duration and each verification step carries only its duration.
73033
+ *
73034
+ * @param steps - Steps which have already finished
73035
+ * @param inProgressStepKind - Step which has started but has not finished yet, appended as `Testing in progress`
72951
73036
  */
72952
- function formatCoderRunSteps(steps) {
72953
- return steps.map(formatCoderRunStep).join('; ');
73037
+ function formatCoderRunSteps(steps, inProgressStepKind) {
73038
+ const formattedSteps = steps.map(formatCoderRunStep);
73039
+ if (inProgressStepKind !== undefined) {
73040
+ formattedSteps.push(`${CODER_RUN_STEP_LABELS[inProgressStepKind]} ${IN_PROGRESS_STEP_SUFFIX}`);
73041
+ }
73042
+ return formattedSteps.join('; ');
72954
73043
  }
72955
73044
  /**
72956
73045
  * Formats one coder run step as `Label $price duration`, omitting the price for steps without model usage.
@@ -72979,47 +73068,78 @@ function formatPromptAttemptMetadata(status, attemptCount) {
72979
73068
  }
72980
73069
 
72981
73070
  /**
72982
- * Replaces the complete todo status line while preserving its indentation.
73071
+ * Builds the shared body of a prompt status line, used by both the in-progress `[^]` and the done `[x]` status.
72983
73072
  *
72984
- * The complete line is replaced because a todo status can contain a required
72985
- * model/harness token in addition to priority markers.
73073
+ * Produces details such as
73074
+ * ``by OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
72986
73075
  */
72987
- function replacePromptTodoStatusLine(line, replacementStatusLine) {
72988
- return line.replace(/^(?<indentation>\s*)\[\s*\].*$/u, `$<indentation>${replacementStatusLine}`);
73076
+ function buildPromptStatusDetails(options) {
73077
+ const { steps, inProgressStepKind, runnerName, modelName, attemptCount, loginMethod, thinkingLevel } = options;
73078
+ const runnerSignature = formatRunnerSignature(runnerName, modelName, thinkingLevel);
73079
+ const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
73080
+ const loginMethodLabel = formatCodexLoginMethod(loginMethod);
73081
+ const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
73082
+ const stepsSummary = formatCoderRunSteps(steps, inProgressStepKind);
73083
+ const stepsSuffix = stepsSummary === '' ? '' : ` - ${stepsSummary}`;
73084
+ return `${attemptMetadata}by ${runnerSignature}${loginMethodSuffix}${stepsSuffix}`;
72989
73085
  }
72990
73086
 
72991
73087
  /**
72992
- * Marks a prompt section as done and records the per-step usage pricing and runner details.
73088
+ * Resolves the status line of one prompt section so it can be rewritten.
72993
73089
  */
72994
- function markPromptDone(file, section, steps, runnerName, modelName, attemptCount = 1, loginMethod, thinkingLevel) {
73090
+ function resolvePromptStatusLine(file, section) {
72995
73091
  if (section.statusLineIndex === undefined) {
72996
- throw new Error(`Prompt ${section.index + 1} in ${file.name} does not have a status line.`);
73092
+ throw new UnexpectedError(spaceTrim$1(`
73093
+ Prompt ${section.index + 1} in \`${file.name}\` does not have a status line.
73094
+ `));
72997
73095
  }
72998
73096
  const line = file.lines[section.statusLineIndex];
72999
73097
  if (line === undefined) {
73000
- throw new Error(`Prompt ${section.index + 1} in ${file.name} points to a missing status line.`);
73098
+ throw new UnexpectedError(spaceTrim$1(`
73099
+ Prompt ${section.index + 1} in \`${file.name}\` points to a missing status line.
73100
+ `));
73001
73101
  }
73002
- const runnerSignature = formatRunnerSignature(runnerName, modelName, thinkingLevel);
73003
- const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
73004
- const loginMethodLabel = formatCodexLoginMethod(loginMethod);
73005
- const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
73006
- const stepsSummary = formatCoderRunSteps(steps);
73007
- const stepsSuffix = stepsSummary === '' ? '' : ` - ${stepsSummary}`;
73008
- // Replace the complete todo status, including any required model/harness token.
73009
- file.lines[section.statusLineIndex] = replacePromptTodoStatusLine(line, `[x] ${attemptMetadata}by ${runnerSignature}${loginMethodSuffix}${stepsSuffix}`);
73102
+ return { statusLineIndex: section.statusLineIndex, line };
73103
+ }
73104
+
73105
+ /**
73106
+ * Matches a complete todo `[ ]` or in-progress `[^]` status line, capturing its indentation.
73107
+ *
73108
+ * The complete line is replaced because a todo status can contain a required model/harness token
73109
+ * in addition to its priority markers, and an in-progress status already carries the metadata of
73110
+ * the steps recorded so far.
73111
+ */
73112
+ const REWRITABLE_PROMPT_STATUS_LINE_PATTERN = /^(?<indentation>\s*)\[(?:\s*|\^)\].*$/u;
73113
+ /**
73114
+ * Rewrites the status line of one prompt section while preserving its indentation.
73115
+ *
73116
+ * Only a todo `[ ]` or an in-progress `[^]` status line is rewritten, so an already finalized
73117
+ * `[x]`, `[!]` or `[-]` status is never overwritten by accident.
73118
+ */
73119
+ function writePromptStatusLine(file, section, replacementStatusLine) {
73120
+ const { statusLineIndex, line } = resolvePromptStatusLine(file, section);
73121
+ file.lines[statusLineIndex] = line.replace(REWRITABLE_PROMPT_STATUS_LINE_PATTERN, `$<indentation>${replacementStatusLine}`);
73122
+ }
73123
+
73124
+ /**
73125
+ * Marks a prompt section as done and records the per-step usage pricing and runner details.
73126
+ */
73127
+ function markPromptDone(file, section, steps, runnerName, modelName, attemptCount = 1, loginMethod, thinkingLevel) {
73128
+ const statusDetails = buildPromptStatusDetails({
73129
+ steps,
73130
+ runnerName,
73131
+ modelName,
73132
+ attemptCount,
73133
+ loginMethod,
73134
+ thinkingLevel,
73135
+ });
73136
+ writePromptStatusLine(file, section, `[x] ${statusDetails}`);
73010
73137
  }
73011
73138
 
73012
73139
  /**
73013
73140
  * Marks a prompt section as failed and records runner details.
73014
73141
  */
73015
73142
  function markPromptFailed(file, section, runnerName, modelName, promptExecutionStartedDate, attemptCount = 1) {
73016
- if (section.statusLineIndex === undefined) {
73017
- throw new Error(`Prompt ${section.index + 1} in ${file.name} does not have a status line.`);
73018
- }
73019
- const line = file.lines[section.statusLineIndex];
73020
- if (line === undefined) {
73021
- throw new Error(`Prompt ${section.index + 1} in ${file.name} points to a missing status line.`);
73022
- }
73023
73143
  const runnerSignature = formatRunnerSignature(runnerName, modelName);
73024
73144
  const attemptMetadata = formatPromptAttemptMetadata('failed', attemptCount);
73025
73145
  const duration = moment().diff(promptExecutionStartedDate);
@@ -73027,7 +73147,18 @@ function markPromptFailed(file, section, runnerName, modelName, promptExecutionS
73027
73147
  const failureDetails = attemptMetadata === ''
73028
73148
  ? `failed after ${durationString} by ${runnerSignature}`
73029
73149
  : `${attemptMetadata}${durationString} by ${runnerSignature}`;
73030
- file.lines[section.statusLineIndex] = replacePromptTodoStatusLine(line, `[!] ${failureDetails}`);
73150
+ writePromptStatusLine(file, section, `[!] ${failureDetails}`);
73151
+ }
73152
+
73153
+ /**
73154
+ * Marks a prompt section as being implemented right now and records the steps finished so far.
73155
+ *
73156
+ * The `[^]` status is deliberately never reverted: when the coder is killed or crashes, the status stays
73157
+ * in the prompt file as the signal that this task was left in the middle of its implementation.
73158
+ */
73159
+ function markPromptInProgress(options) {
73160
+ const { file, section, ...statusDetailsOptions } = options;
73161
+ writePromptStatusLine(file, section, `[^] ${buildPromptStatusDetails(statusDetailsOptions)}`);
73031
73162
  }
73032
73163
 
73033
73164
  /**
@@ -73136,6 +73267,13 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
73136
73267
  attemptCount = nextAttemptCount;
73137
73268
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
73138
73269
  },
73270
+ onStepStarted: (progress) => recordPromptRoundInProgress({
73271
+ nextPrompt,
73272
+ runnerMetadata,
73273
+ thinkingLevel: options.thinkingLevel,
73274
+ attemptCount,
73275
+ progress,
73276
+ }),
73139
73277
  waitForPauseCheckpoint: waitForRequestedPause,
73140
73278
  });
73141
73279
  await finalizeSuccessfulPromptRound({
@@ -73187,6 +73325,30 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
73187
73325
  throw lastError;
73188
73326
  }, { preserveArtifactsOnSuccess: options.preserveLogs });
73189
73327
  }
73328
+ /**
73329
+ * Records into the prompt file that the prompt is being implemented right now.
73330
+ *
73331
+ * The `[^]` in-progress status is written before every single step, so it always names the step which is
73332
+ * running and the steps already finished. It is intentionally never reverted: a coder which is killed or
73333
+ * crashes leaves the `[^]` status behind as the signal that this task was left in the middle.
73334
+ */
73335
+ async function recordPromptRoundInProgress(options) {
73336
+ const { nextPrompt, runnerMetadata, thinkingLevel, attemptCount, progress } = options;
73337
+ markPromptInProgress({
73338
+ file: nextPrompt.file,
73339
+ section: nextPrompt.section,
73340
+ steps: progress.finishedSteps,
73341
+ inProgressStepKind: progress.startedStepKind,
73342
+ runnerName: runnerMetadata.runnerName,
73343
+ modelName: runnerMetadata.modelName,
73344
+ attemptCount,
73345
+ loginMethod: progress.loginMethod,
73346
+ thinkingLevel,
73347
+ });
73348
+ // Note: The prompt status is always written into the original project, an isolated round transports
73349
+ // its own changes back through the merge instead
73350
+ await writePromptFile(nextPrompt.file);
73351
+ }
73190
73352
  /**
73191
73353
  * Sleeps `options.waitAfterError` while keeping the rich UI and plain console in sync, then resets state for the retry.
73192
73354
  */
@@ -73847,19 +74009,9 @@ const PROMPT_STATUS_BOX_PATTERN = /^(?<indentation>\s*)\[[^\]]*\]/u;
73847
74009
  * kept and only the status box turns from `[x]` into `[!]`, followed by the manual merge instructions.
73848
74010
  */
73849
74011
  function markPromptIsolationMergeFailed(file, section, worktree) {
73850
- if (section.statusLineIndex === undefined) {
73851
- throw new UnexpectedError(spaceTrim$1(`
73852
- Prompt ${section.index + 1} in \`${file.name}\` does not have a status line.
73853
- `));
73854
- }
73855
- const line = file.lines[section.statusLineIndex];
73856
- if (line === undefined) {
73857
- throw new UnexpectedError(spaceTrim$1(`
73858
- Prompt ${section.index + 1} in \`${file.name}\` points to a missing status line.
73859
- `));
73860
- }
74012
+ const { statusLineIndex, line } = resolvePromptStatusLine(file, section);
73861
74013
  const failedStatusLine = line.replace(PROMPT_STATUS_BOX_PATTERN, '$<indentation>[!]');
73862
- file.lines[section.statusLineIndex] = `${failedStatusLine} - ${buildCoderIsolationMergeFailureStatusNote(worktree)}`;
74014
+ file.lines[statusLineIndex] = `${failedStatusLine} - ${buildCoderIsolationMergeFailureStatusNote(worktree)}`;
73863
74015
  }
73864
74016
 
73865
74017
  /**
@@ -74999,7 +75151,9 @@ function getPromptColumn(options) {
74999
75151
  if (section.status === 'not-ready' || isUnwritten) {
75000
75152
  return 'backlog';
75001
75153
  }
75002
- if (isActive) {
75154
+ // Note: A prompt marked `[^]` stays in progress even when no agent is running it, because its
75155
+ // implementation was left in the middle
75156
+ if (isActive || section.status === 'in-progress') {
75003
75157
  return 'in-progress';
75004
75158
  }
75005
75159
  if (!isPromptInPriorityFilter(section, priorityFilter)) {
@@ -75016,6 +75170,10 @@ function buildPromptTags(options) {
75016
75170
  if (options.section.status === 'not-ready') {
75017
75171
  tags.push({ id: 'not-ready', label: '[-]' });
75018
75172
  }
75173
+ // Note: A `[^]` prompt which no agent is running right now was left in the middle of its implementation
75174
+ if (options.section.status === 'in-progress' && !options.isActive) {
75175
+ tags.push({ id: 'left-in-progress', label: '[^]' });
75176
+ }
75019
75177
  if (options.isUnwritten) {
75020
75178
  tags.push({ id: 'unwritten', label: '@@@' });
75021
75179
  }
@@ -75045,7 +75203,7 @@ function getActivePrompt(uiState) {
75045
75203
  * Checks whether a parsed section is the prompt currently handled by the active agent.
75046
75204
  */
75047
75205
  function isPromptActive(promptFile, section, activePrompt) {
75048
- if (!activePrompt || section.status !== 'todo') {
75206
+ if (!activePrompt || (section.status !== 'todo' && section.status !== 'in-progress')) {
75049
75207
  return false;
75050
75208
  }
75051
75209
  return buildPromptLabelForDisplay(promptFile, section) === activePrompt.label;
@@ -75077,7 +75235,7 @@ function buildCoderServerRunState(uiState) {
75077
75235
  * Overwrites the body of one prompt section with new content, preserving the status line.
75078
75236
  *
75079
75237
  * The `newContent` string is the prompt text without the status marker.
75080
- * The status line (`[ ]`, `[x]`, `[!]`, `[-]`) is kept intact.
75238
+ * The status line (`[ ]`, `[^]`, `[x]`, `[!]`, `[-]`) is kept intact.
75081
75239
  *
75082
75240
  * @private internal utility of `ptbk coder server`
75083
75241
  */
@@ -75358,6 +75516,7 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
75358
75516
  line-height: 1.35;
75359
75517
  }
75360
75518
  .tag-not-ready { background: #e2e8f0; color: #475569; }
75519
+ .tag-left-in-progress { background: #ffe4e6; color: #9f1239; }
75361
75520
  .tag-unwritten { background: #fff4cc; color: #8a5a00; }
75362
75521
  .tag-implementing { background: #ede9fe; color: #5b21b6; }
75363
75522
  .tag-verifying { background: #fae8ff; color: #86198f; }
@@ -76088,6 +76247,33 @@ var runCodexPromptsServer$1 = /*#__PURE__*/Object.freeze({
76088
76247
  runCodexPromptsServer: runCodexPromptsServer
76089
76248
  });
76090
76249
 
76250
+ /**
76251
+ * Checks whether one prompt section still has open coding work.
76252
+ *
76253
+ * A `[ ]` prompt has not been started at all and a `[^]` prompt was left in the middle of its
76254
+ * implementation, so a file containing either of them is not finished yet.
76255
+ */
76256
+ function isPromptSectionUnfinished(section) {
76257
+ return section.status === 'todo' || section.status === 'in-progress';
76258
+ }
76259
+
76260
+ /**
76261
+ * Orders the loaded prompt files for one verification pass.
76262
+ *
76263
+ * Note: `$` is used to indicate that this function is not a pure function - the `random` order is not deterministic
76264
+ * Note: This function does NOT mutate the given array
76265
+ */
76266
+ function $orderPromptFiles(promptFiles, order) {
76267
+ switch (order) {
76268
+ case 'from-earliest':
76269
+ return [...promptFiles];
76270
+ case 'from-latest':
76271
+ return [...promptFiles].reverse();
76272
+ case 'random':
76273
+ return [...$shuffleItems(...promptFiles)];
76274
+ }
76275
+ }
76276
+
76091
76277
  /**
76092
76278
  * Path to the directory that holds the prompt markdown files.
76093
76279
  */
@@ -76116,8 +76302,8 @@ const DEFAULT_VERIFY_PROMPTS_OPTIONS = normalizeVerifyPromptsOptions(parseVerify
76116
76302
  async function verifyPrompts(options = DEFAULT_VERIFY_PROMPTS_OPTIONS) {
76117
76303
  const normalizedOptions = normalizeVerifyPromptsOptions(options);
76118
76304
  console.info(colors.cyan.bold('📋 Prompt verification helper'));
76119
- if (normalizedOptions.reverse) {
76120
- console.info(colors.gray('Processing files in reverse order'));
76305
+ if (normalizedOptions.order !== DEFAULT_VERIFY_PROMPTS_ORDER) {
76306
+ console.info(colors.gray(`Processing files ${VERIFY_PROMPTS_ORDER_DESCRIPTIONS[normalizedOptions.order]}`));
76121
76307
  }
76122
76308
  if (normalizedOptions.ignore.length > 0) {
76123
76309
  console.info(colors.gray(`Ignoring candidates matching: ${normalizedOptions.ignore.join(', ')}`));
@@ -76174,7 +76360,7 @@ async function $commitVerificationOutcome(gitSync, outcome) {
76174
76360
  */
76175
76361
  function parseVerifyPromptsCliOptions(args) {
76176
76362
  return {
76177
- reverse: args.includes('--reverse'),
76363
+ order: parseVerifyPromptsOrder(readStringOption(args, '--order')),
76178
76364
  ignore: readRepeatableStringOption(args, '--ignore'),
76179
76365
  gitSync: {
76180
76366
  isCommitEnabled: args.includes('--commit'),
@@ -76189,10 +76375,7 @@ function parseVerifyPromptsCliOptions(args) {
76189
76375
  async function loadPromptFilesForVerification(options) {
76190
76376
  const loadedPromptFiles = await loadPromptFiles(PROMPTS_DIR);
76191
76377
  const { promptFiles, ignoredPromptFiles } = partitionPromptFilesByIgnore(loadedPromptFiles, options.ignore);
76192
- if (options.reverse) {
76193
- promptFiles.reverse();
76194
- }
76195
- return { promptFiles, ignoredPromptFiles };
76378
+ return { promptFiles: $orderPromptFiles(promptFiles, options.order), ignoredPromptFiles };
76196
76379
  }
76197
76380
  /**
76198
76381
  * Splits prompt files into files that should be verified now and files ignored for this run.
@@ -76234,7 +76417,7 @@ async function prepareArchiveDirectory() {
76234
76417
  function normalizeVerifyPromptsOptions(options) {
76235
76418
  var _a, _b, _c;
76236
76419
  return {
76237
- reverse: (_a = options.reverse) !== null && _a !== void 0 ? _a : false,
76420
+ order: (_a = options.order) !== null && _a !== void 0 ? _a : DEFAULT_VERIFY_PROMPTS_ORDER,
76238
76421
  ignore: normalizeIgnoreValues((_b = options.ignore) !== null && _b !== void 0 ? _b : []),
76239
76422
  gitSync: (_c = options.gitSync) !== null && _c !== void 0 ? _c : DISABLED_CODER_GIT_SYNC_OPTIONS,
76240
76423
  };
@@ -76259,6 +76442,13 @@ function normalizeIgnoreValues(ignoreValues) {
76259
76442
  }
76260
76443
  return normalizedIgnoreValues;
76261
76444
  }
76445
+ /**
76446
+ * Reads one single-value string option from raw CLI arguments, keeping the last occurrence.
76447
+ */
76448
+ function readStringOption(args, flag) {
76449
+ const values = readRepeatableStringOption(args, flag);
76450
+ return values[values.length - 1];
76451
+ }
76262
76452
  /**
76263
76453
  * Reads one repeatable string option from raw CLI arguments.
76264
76454
  */
@@ -76320,8 +76510,9 @@ function displayTopLevelFileList(promptFiles) {
76320
76510
  for (const file of promptFiles) {
76321
76511
  const doneCount = file.sections.filter((section) => section.status === 'done').length;
76322
76512
  const todoCount = file.sections.filter((section) => section.status === 'todo').length;
76513
+ const inProgressCount = file.sections.filter((section) => section.status === 'in-progress').length;
76323
76514
  const notReadyCount = file.sections.filter((section) => section.status === 'not-ready').length;
76324
- const needsVerification = file.sections.length > 0 && todoCount === 0 && doneCount > 0;
76515
+ const needsVerification = file.sections.length > 0 && !file.sections.some(isPromptSectionUnfinished) && doneCount > 0;
76325
76516
  const statusParts = [];
76326
76517
  if (needsVerification) {
76327
76518
  statusParts.push(colors.cyan.bold(`🔍 ${doneCount} done [x] - NEEDS VERIFICATION`));
@@ -76330,6 +76521,9 @@ function displayTopLevelFileList(promptFiles) {
76330
76521
  if (todoCount > 0) {
76331
76522
  statusParts.push(colors.yellow(`${todoCount} todo [ ]`));
76332
76523
  }
76524
+ if (inProgressCount > 0) {
76525
+ statusParts.push(colors.magenta(`${inProgressCount} in-progress [^]`));
76526
+ }
76333
76527
  if (doneCount > 0) {
76334
76528
  statusParts.push(colors.green(`${doneCount} done [x]`));
76335
76529
  }
@@ -76345,7 +76539,7 @@ function displayTopLevelFileList(promptFiles) {
76345
76539
  console.info(colors.gray('Goal: verify all done prompts, then process remaining todo prompts.'));
76346
76540
  }
76347
76541
  /**
76348
- * Finds the first file where at least one prompt is marked as done [x] and no prompts are todo [ ].
76542
+ * Finds the first file where at least one prompt is marked as done [x] and no prompts are todo [ ] or in-progress [^].
76349
76543
  * Completely ignores not-ready prompts like [-], [.], [?], etc.
76350
76544
  * Also excludes files that have been skipped in this session.
76351
76545
  */
@@ -76358,10 +76552,10 @@ function findFileWithAllDonePrompts(promptFiles, skippedFiles) {
76358
76552
  if (skippedFiles.has(file.path)) {
76359
76553
  return false;
76360
76554
  }
76361
- // File is ready for verification if it has at least one done prompt and no todo prompts
76362
- const hasTodoPrompts = file.sections.some((section) => section.status === 'todo');
76555
+ // File is ready for verification if it has at least one done prompt and no unfinished prompts
76556
+ const hasUnfinishedPrompts = file.sections.some(isPromptSectionUnfinished);
76363
76557
  const hasDonePrompts = file.sections.some((section) => section.status === 'done');
76364
- return !hasTodoPrompts && hasDonePrompts;
76558
+ return !hasUnfinishedPrompts && hasDonePrompts;
76365
76559
  });
76366
76560
  }
76367
76561
  /**
@@ -76467,11 +76661,11 @@ function displayPromptOverview(promptFiles) {
76467
76661
  const filesNeedingVerification = promptFiles.filter((file) => {
76468
76662
  if (file.sections.length === 0)
76469
76663
  return false;
76470
- const hasTodo = file.sections.some((section) => section.status === 'todo');
76664
+ const hasUnfinished = file.sections.some(isPromptSectionUnfinished);
76471
76665
  const hasDone = file.sections.some((section) => section.status === 'done');
76472
- return !hasTodo && hasDone;
76666
+ return !hasUnfinished && hasDone;
76473
76667
  });
76474
- const pendingFiles = promptFiles.filter((file) => file.sections.some((section) => section.status === 'todo'));
76668
+ const pendingFiles = promptFiles.filter((file) => file.sections.some(isPromptSectionUnfinished));
76475
76669
  if (filesNeedingVerification.length) {
76476
76670
  const formattedNames = formatPendingFileNames(filesNeedingVerification.map((file) => file.name));
76477
76671
  console.info(colors.cyan(` 🔍 Files needing verification (${filesNeedingVerification.length}): ${formattedNames}`));