@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
@@ -0,0 +1,48 @@
1
+ import type { CodexLoginMethod } from '../../../src/book-3.0/codexLoginMethod';
2
+ import type { CoderRunStep, CoderRunStepKind } from './CoderRunStep';
3
+ /**
4
+ * Progress of one prompt round, reported right before a new step starts.
5
+ */
6
+ export type CoderRunStepProgress = {
7
+ /**
8
+ * Kind of the step which is about to start.
9
+ */
10
+ readonly startedStepKind: CoderRunStepKind;
11
+ /**
12
+ * Steps of the same prompt round which have already finished, in the order they ran.
13
+ */
14
+ readonly finishedSteps: ReadonlyArray<CoderRunStep>;
15
+ /**
16
+ * Authentication method the harness reported, as soon as one of the finished steps revealed it.
17
+ */
18
+ readonly loginMethod?: CodexLoginMethod;
19
+ };
20
+ /**
21
+ * Notified right before one coder run step starts.
22
+ */
23
+ export type OnCoderRunStepStarted = (progress: CoderRunStepProgress) => Promise<void>;
24
+ /**
25
+ * Collects the measured steps of one prompt round and announces every step which starts.
26
+ */
27
+ export type CoderRunStepTracker = {
28
+ /**
29
+ * Steps which have finished so far, in the order they ran.
30
+ */
31
+ readonly steps: ReadonlyArray<CoderRunStep>;
32
+ /**
33
+ * Announces that one step is about to start.
34
+ */
35
+ readonly startStep: (kind: CoderRunStepKind) => Promise<void>;
36
+ /**
37
+ * Records one finished step together with the authentication method its runner reported.
38
+ */
39
+ readonly finishStep: (step: CoderRunStep, loginMethod?: CodexLoginMethod) => void;
40
+ };
41
+ /**
42
+ * Creates the step tracker of one prompt round.
43
+ *
44
+ * The tracker is the single place which knows which steps have finished and which one is running,
45
+ * so both the finished `[x]` status line and the intermediate `[^]` in-progress status lines are
46
+ * built from the very same data.
47
+ */
48
+ export declare function createCoderRunStepTracker(onStepStarted?: OnCoderRunStepStarted): CoderRunStepTracker;
@@ -0,0 +1,43 @@
1
+ import { type CodexLoginMethod } from '../../../src/book-3.0/codexLoginMethod';
2
+ import type { ThinkingLevel } from '../../../src/cli/cli-commands/coder/ThinkingLevel';
3
+ import type { CoderRunStep, CoderRunStepKind } from '../common/CoderRunStep';
4
+ /**
5
+ * Everything one prompt status line says after its checklist marker.
6
+ */
7
+ export type BuildPromptStatusDetailsOptions = {
8
+ /**
9
+ * Steps of the prompt round which have already finished, each with its own price and duration.
10
+ */
11
+ readonly steps: ReadonlyArray<CoderRunStep>;
12
+ /**
13
+ * Step which has already started but has not finished yet, reported as `Implementation in progress`.
14
+ */
15
+ readonly inProgressStepKind?: CoderRunStepKind;
16
+ /**
17
+ * Harness which runs the prompt.
18
+ */
19
+ readonly runnerName: string | undefined;
20
+ /**
21
+ * Model the harness runs the prompt with.
22
+ */
23
+ readonly modelName: string | undefined;
24
+ /**
25
+ * How many coding attempts the prompt has taken so far.
26
+ */
27
+ readonly attemptCount: number;
28
+ /**
29
+ * Authentication method the harness reported, when it is already known.
30
+ */
31
+ readonly loginMethod?: CodexLoginMethod;
32
+ /**
33
+ * Reasoning effort the harness runs the prompt with.
34
+ */
35
+ readonly thinkingLevel?: ThinkingLevel;
36
+ };
37
+ /**
38
+ * Builds the shared body of a prompt status line, used by both the in-progress `[^]` and the done `[x]` status.
39
+ *
40
+ * Produces details such as
41
+ * ``by OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
42
+ */
43
+ export declare function buildPromptStatusDetails(options: BuildPromptStatusDetailsOptions): string;
@@ -1,9 +1,12 @@
1
- import type { CoderRunStep } from '../common/CoderRunStep';
1
+ import type { CoderRunStep, CoderRunStepKind } from '../common/CoderRunStep';
2
2
  /**
3
- * Formats the per-step usage breakdown recorded for one finished prompt.
3
+ * Formats the per-step usage breakdown recorded for one prompt.
4
4
  *
5
5
  * Produces a `; `-separated summary such as
6
6
  * `Implementation $8.01 6 hours; Testing 1 hour; Fixing $3.14 2 hours` where each coding step carries its
7
7
  * price and duration and each verification step carries only its duration.
8
+ *
9
+ * @param steps - Steps which have already finished
10
+ * @param inProgressStepKind - Step which has started but has not finished yet, appended as `Testing in progress`
8
11
  */
9
- export declare function formatCoderRunSteps(steps: ReadonlyArray<CoderRunStep>): string;
12
+ export declare function formatCoderRunSteps(steps: ReadonlyArray<CoderRunStep>, inProgressStepKind?: CoderRunStepKind): string;
@@ -0,0 +1,8 @@
1
+ import type { PromptSection } from './types/PromptSection';
2
+ /**
3
+ * Checks whether one prompt section still has open coding work.
4
+ *
5
+ * A `[ ]` prompt has not been started at all and a `[^]` prompt was left in the middle of its
6
+ * implementation, so a file containing either of them is not finished yet.
7
+ */
8
+ export declare function isPromptSectionUnfinished(section: PromptSection): boolean;
@@ -1,4 +1,4 @@
1
- import { type CodexLoginMethod } from '../../../src/book-3.0/codexLoginMethod';
1
+ import type { CodexLoginMethod } from '../../../src/book-3.0/codexLoginMethod';
2
2
  import type { ThinkingLevel } from '../../../src/cli/cli-commands/coder/ThinkingLevel';
3
3
  import type { CoderRunStep } from '../common/CoderRunStep';
4
4
  import type { PromptFile } from './types/PromptFile';
@@ -0,0 +1,27 @@
1
+ import { type BuildPromptStatusDetailsOptions } from './buildPromptStatusDetails';
2
+ import type { PromptFile } from './types/PromptFile';
3
+ import type { PromptSection } from './types/PromptSection';
4
+ /**
5
+ * Input for marking one prompt section as being implemented right now.
6
+ */
7
+ export type MarkPromptInProgressOptions = BuildPromptStatusDetailsOptions & {
8
+ /**
9
+ * Prompt file the marked section belongs to.
10
+ */
11
+ readonly file: PromptFile;
12
+ /**
13
+ * Section which is being implemented right now.
14
+ */
15
+ readonly section: PromptSection;
16
+ /**
17
+ * Step which has just started, always present because a prompt is only in progress while a step runs.
18
+ */
19
+ readonly inProgressStepKind: NonNullable<BuildPromptStatusDetailsOptions['inProgressStepKind']>;
20
+ };
21
+ /**
22
+ * Marks a prompt section as being implemented right now and records the steps finished so far.
23
+ *
24
+ * The `[^]` status is deliberately never reverted: when the coder is killed or crashes, the status stays
25
+ * in the prompt file as the signal that this task was left in the middle of its implementation.
26
+ */
27
+ export declare function markPromptInProgress(options: MarkPromptInProgressOptions): void;
@@ -0,0 +1,19 @@
1
+ import type { PromptFile } from './types/PromptFile';
2
+ import type { PromptSection } from './types/PromptSection';
3
+ /**
4
+ * Status line of one prompt section together with the index it lives on.
5
+ */
6
+ export type ResolvedPromptStatusLine = {
7
+ /**
8
+ * Index of the status line within `file.lines`.
9
+ */
10
+ readonly statusLineIndex: number;
11
+ /**
12
+ * Current content of the status line, including its indentation and checklist marker.
13
+ */
14
+ readonly line: string;
15
+ };
16
+ /**
17
+ * Resolves the status line of one prompt section so it can be rewritten.
18
+ */
19
+ export declare function resolvePromptStatusLine(file: PromptFile, section: PromptSection): ResolvedPromptStatusLine;
@@ -0,0 +1,9 @@
1
+ import type { PromptFile } from './types/PromptFile';
2
+ import type { PromptSection } from './types/PromptSection';
3
+ /**
4
+ * Rewrites the status line of one prompt section while preserving its indentation.
5
+ *
6
+ * Only a todo `[ ]` or an in-progress `[^]` status line is rewritten, so an already finalized
7
+ * `[x]`, `[!]` or `[-]` status is never overwritten by accident.
8
+ */
9
+ export declare function writePromptStatusLine(file: PromptFile, section: PromptSection, replacementStatusLine: string): void;
@@ -10,7 +10,7 @@ export type CoderServerBoardColumn = 'backlog' | 'low-priority' | 'todo' | 'in-p
10
10
  * UI tag attached to one prompt card.
11
11
  */
12
12
  export type CoderServerPromptTag = {
13
- readonly id: 'not-ready' | 'unwritten' | 'implementing' | 'verifying';
13
+ readonly id: 'not-ready' | 'unwritten' | 'left-in-progress' | 'implementing' | 'verifying';
14
14
  readonly label: string;
15
15
  };
16
16
  /**
@@ -6,4 +6,4 @@
6
6
  *
7
7
  * @private internal constant of `ptbk coder server`
8
8
  */
9
- export declare const CODER_SERVER_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Ptbk Coder Server</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n background: #f6f7f9;\n color: #1f2933;\n min-height: 100vh;\n }\n\n header {\n background: #22313f;\n color: white;\n padding: 12px 18px;\n display: flex;\n align-items: center;\n gap: 12px;\n position: sticky;\n top: 0;\n z-index: 10;\n box-shadow: 0 2px 10px rgba(15,23,42,0.22);\n }\n header h1 { font-size: 17px; font-weight: 700; flex-shrink: 0; letter-spacing: 0; }\n\n .status-badge {\n padding: 3px 10px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n flex-shrink: 0;\n }\n .status-RUNNING { background: #0b875b; color: white; }\n .status-PAUSING { background: #d97904; color: white; }\n .status-PAUSED { background: #b42318; color: white; }\n\n #pause-label {\n font-size: 12px;\n color: rgba(255,255,255,0.75);\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .btn {\n padding: 6px 12px;\n border: 0;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 700;\n transition: background 0.15s, opacity 0.15s;\n flex-shrink: 0;\n }\n .btn:disabled { opacity: 0.55; cursor: default; }\n .btn-pause { background: #ffd166; color: #1f2933; }\n .btn-resume { background: #0b875b; color: white; }\n .btn-save { background: #2563eb; color: white; }\n .btn-cancel { background: #edf0f4; color: #1f2933; }\n\n .run-strip {\n background: white;\n border-bottom: 1px solid #d8dee8;\n padding: 12px 18px;\n display: grid;\n grid-template-columns: minmax(220px, 1.2fr) minmax(240px, 1fr) minmax(260px, 1.4fr);\n gap: 14px;\n align-items: center;\n }\n .run-title {\n font-size: 14px;\n font-weight: 700;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .run-subtitle,\n .run-current,\n .run-output {\n color: #64748b;\n font-size: 12px;\n line-height: 1.45;\n overflow: hidden;\n }\n .run-current,\n .run-output {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n }\n .progress-shell {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 8px;\n align-items: center;\n }\n .progress-track {\n height: 10px;\n background: #e4e9f0;\n border-radius: 999px;\n overflow: hidden;\n }\n .progress-fill {\n display: block;\n height: 100%;\n width: 0;\n background: linear-gradient(90deg, #0b875b, #2563eb);\n transition: width 0.2s;\n }\n .progress-label {\n color: #475569;\n font-size: 12px;\n font-weight: 700;\n min-width: 72px;\n text-align: right;\n }\n\n .board {\n display: flex;\n gap: 14px;\n padding: 16px;\n overflow-x: auto;\n min-height: calc(100vh - 119px);\n align-items: flex-start;\n }\n\n .column {\n background: #e9edf3;\n border-top: 4px solid #94a3b8;\n border-radius: 6px;\n padding: 10px;\n min-width: 250px;\n width: 270px;\n flex-shrink: 0;\n }\n .column-backlog { border-top-color: #64748b; }\n .column-low-priority { border-top-color: #d97904; }\n .column-todo { border-top-color: #2563eb; }\n .column-in-progress { border-top-color: #7c3aed; }\n .column-done { border-top-color: #0b875b; }\n .column-errors { border-top-color: #b42318; }\n .column-finished { border-top-color: #0891b2; }\n\n .column-header {\n font-size: 12px;\n font-weight: 800;\n text-transform: uppercase;\n color: #475569;\n margin-bottom: 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .column-count {\n background: rgba(255,255,255,0.72);\n border-radius: 999px;\n padding: 2px 8px;\n font-size: 11px;\n color: #475569;\n }\n .column-cards { min-height: 40px; }\n\n .card {\n background: white;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 8px;\n box-shadow: 0 1px 3px rgba(15,23,42,0.12);\n cursor: pointer;\n border-left: 3px solid #94a3b8;\n }\n .card:hover { box-shadow: 0 4px 12px rgba(15,23,42,0.16); }\n .card-backlog { border-left-color: #64748b; }\n .card-low-priority { border-left-color: #d97904; }\n .card-todo { border-left-color: #2563eb; }\n .card-in-progress { border-left-color: #7c3aed; }\n .card-done { border-left-color: #0b875b; }\n .card-errors { border-left-color: #b42318; }\n .card-finished { border-left-color: #0891b2; }\n\n .card-file {\n font-size: 10px;\n color: #7b8794;\n margin-bottom: 6px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .card-summary {\n font-size: 13px;\n line-height: 1.45;\n color: #1f2933;\n word-break: break-word;\n white-space: pre-wrap;\n max-height: 76px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 4;\n -webkit-box-orient: vertical;\n }\n .card-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n margin-top: 8px;\n }\n .card-tag {\n border-radius: 999px;\n padding: 2px 7px;\n font-size: 10px;\n font-weight: 800;\n line-height: 1.35;\n }\n .tag-not-ready { background: #e2e8f0; color: #475569; }\n .tag-unwritten { background: #fff4cc; color: #8a5a00; }\n .tag-implementing { background: #ede9fe; color: #5b21b6; }\n .tag-verifying { background: #fae8ff; color: #86198f; }\n .tag-priority { background: #ffedd5; color: #9a3412; }\n\n .empty-column {\n color: #94a3b8;\n font-size: 12px;\n padding: 10px 4px;\n text-align: center;\n }\n\n .modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(15,23,42,0.58);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 56px 16px 16px;\n z-index: 100;\n }\n .modal-overlay.hidden { display: none; }\n\n .modal {\n background: white;\n border-radius: 8px;\n width: 100%;\n max-width: 720px;\n padding: 18px;\n box-shadow: 0 14px 40px rgba(15,23,42,0.28);\n }\n .modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n margin-bottom: 14px;\n gap: 12px;\n }\n .modal-meta { flex: 1; min-width: 0; }\n .modal-file {\n font-size: 11px;\n color: #7b8794;\n margin-bottom: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .modal-section-label { font-size: 14px; font-weight: 700; color: #1f2933; }\n .modal-status {\n font-size: 11px;\n font-weight: 800;\n padding: 3px 8px;\n border-radius: 999px;\n text-transform: uppercase;\n flex-shrink: 0;\n }\n .status-backlog { background: #e2e8f0; color: #475569; }\n .status-low-priority { background: #ffedd5; color: #9a3412; }\n .status-todo { background: #dbeafe; color: #1d4ed8; }\n .status-in-progress { background: #ede9fe; color: #5b21b6; }\n .status-done { background: #dcfce7; color: #166534; }\n .status-errors { background: #fee2e2; color: #991b1b; }\n .status-finished { background: #cffafe; color: #155e75; }\n .modal-close {\n background: transparent;\n border: 0;\n color: #64748b;\n cursor: pointer;\n font-size: 20px;\n padding: 0 4px;\n line-height: 1;\n }\n .modal-close:hover { color: #1f2933; }\n\n textarea {\n width: 100%;\n min-height: 300px;\n font-family: \"SFMono-Regular\", Consolas, \"Courier New\", monospace;\n font-size: 13px;\n border: 2px solid #d8dee8;\n border-radius: 4px;\n padding: 10px 12px;\n resize: vertical;\n line-height: 1.6;\n color: #1f2933;\n }\n textarea:focus {\n outline: none;\n border-color: #2563eb;\n box-shadow: 0 0 0 3px rgba(37,99,235,0.12);\n }\n .modal-actions {\n display: flex;\n gap: 8px;\n margin-top: 14px;\n justify-content: flex-end;\n }\n\n .error-banner {\n background: #fee2e2;\n border-bottom: 2px solid #b42318;\n color: #991b1b;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 700;\n }\n .error-banner.hidden { display: none; }\n\n @media (max-width: 860px) {\n header { flex-wrap: wrap; }\n #pause-label { order: 4; flex-basis: 100%; }\n .run-strip { grid-template-columns: 1fr; }\n .board { min-height: calc(100vh - 212px); }\n }\n </style>\n</head>\n<body>\n <div id=\"error-banner\" class=\"error-banner hidden\"></div>\n\n <header>\n <h1>Ptbk Coder Server</h1>\n <span id=\"status-badge\" class=\"status-badge status-RUNNING\">RUNNING</span>\n <span id=\"pause-label\"></span>\n <button id=\"toggle-btn\" class=\"btn btn-pause\">Pause</button>\n </header>\n\n <section class=\"run-strip\">\n <div>\n <div class=\"run-title\" id=\"run-title\">Initializing...</div>\n <div class=\"run-subtitle\" id=\"run-subtitle\">Loading runner state</div>\n </div>\n <div class=\"progress-shell\">\n <div class=\"progress-track\"><span class=\"progress-fill\" id=\"progress-fill\"></span></div>\n <div class=\"progress-label\" id=\"progress-label\">0%</div>\n </div>\n <div>\n <div class=\"run-current\" id=\"run-current\">No active prompt</div>\n <div class=\"run-output\" id=\"run-output\"></div>\n </div>\n </section>\n\n <div class=\"board\" id=\"board\"></div>\n\n <div class=\"modal-overlay hidden\" id=\"modal-overlay\">\n <div class=\"modal\">\n <div class=\"modal-header\">\n <div class=\"modal-meta\">\n <div class=\"modal-file\" id=\"modal-file\"></div>\n <div class=\"modal-section-label\" id=\"modal-section-label\"></div>\n </div>\n <span class=\"modal-status\" id=\"modal-status-badge\"></span>\n <button class=\"modal-close\" id=\"modal-close\" title=\"Close\">&times;</button>\n </div>\n\n <textarea id=\"modal-content\" placeholder=\"Prompt content\"></textarea>\n\n <div class=\"modal-actions\">\n <button class=\"btn btn-cancel\" id=\"cancel-button\">Cancel</button>\n <button class=\"btn btn-save\" id=\"save-button\">Save</button>\n </div>\n </div>\n </div>\n\n <script>\n \"use strict\";\n\n const BOARD_COLUMNS = [\n { id: \"backlog\", title: \"Backlog\" },\n { id: \"low-priority\", title: \"Outside priority\" },\n { id: \"todo\", title: \"To do\" },\n { id: \"in-progress\", title: \"In progress\" },\n { id: \"done\", title: \"Done\" },\n { id: \"errors\", title: \"Errors\" },\n { id: \"finished\", title: \"Finished\" },\n ];\n\n let modalState = null;\n let lastPauseState = \"RUNNING\";\n\n function getColumnTitle(columnId) {\n const column = BOARD_COLUMNS.find((columnCandidate) => columnCandidate.id === columnId);\n return column ? column.title : columnId;\n }\n\n function showError(message) {\n const banner = document.getElementById(\"error-banner\");\n banner.textContent = message;\n banner.classList.remove(\"hidden\");\n clearTimeout(banner.timer);\n banner.timer = setTimeout(() => banner.classList.add(\"hidden\"), 6000);\n }\n\n function escapeHtml(value) {\n const element = document.createElement(\"div\");\n element.textContent = String(value);\n return element.innerHTML;\n }\n\n function renderBoardSkeleton() {\n const board = document.getElementById(\"board\");\n board.innerHTML = \"\";\n\n for (const column of BOARD_COLUMNS) {\n const columnElement = document.createElement(\"section\");\n columnElement.className = \"column column-\" + column.id;\n columnElement.innerHTML =\n '<div class=\"column-header\">' +\n '<span>' + escapeHtml(column.title) + '</span>' +\n '<span class=\"column-count\" id=\"count-' + column.id + '\">0</span>' +\n '</div>' +\n '<div class=\"column-cards\" id=\"cards-' + column.id + '\">' +\n '<div class=\"empty-column\">Loading</div>' +\n '</div>';\n board.appendChild(columnElement);\n }\n }\n\n async function fetchStatus() {\n try {\n const response = await fetch(\"/api/status\");\n if (!response.ok) {\n showError(\"Status API error: \" + response.status);\n return;\n }\n\n const status = await response.json();\n renderPauseState(status);\n renderRunState(status.runState);\n } catch (error) {\n showError(\"Could not reach coder server: \" + error.message);\n }\n }\n\n function renderPauseState(status) {\n lastPauseState = status.pauseState;\n\n const badge = document.getElementById(\"status-badge\");\n badge.textContent = status.pauseState;\n badge.className = \"status-badge status-\" + status.pauseState;\n\n const toggleButton = document.getElementById(\"toggle-btn\");\n const pauseLabel = document.getElementById(\"pause-label\");\n\n if (status.pauseState === \"RUNNING\") {\n toggleButton.textContent = \"Pause\";\n toggleButton.className = \"btn btn-pause\";\n pauseLabel.textContent = \"\";\n return;\n }\n\n toggleButton.textContent = \"Resume\";\n toggleButton.className = \"btn btn-resume\";\n pauseLabel.textContent =\n (status.pauseState === \"PAUSING\" ? \"Pausing before: \" : \"Paused before: \") +\n (status.pauseTargetLabel || \"next checkpoint\");\n }\n\n function renderRunState(runState) {\n if (!runState) {\n document.getElementById(\"run-title\").textContent = \"Waiting for runner state\";\n document.getElementById(\"run-subtitle\").textContent = \"\";\n return;\n }\n\n const progress = runState.progress || {};\n const percentage = Number(progress.percentage || 0);\n const runnerParts = [runState.config.agentName, runState.config.modelName, runState.config.thinkingLevel]\n .filter(Boolean);\n\n document.getElementById(\"run-title\").textContent = runState.statusMessage || runState.phase;\n document.getElementById(\"run-subtitle\").textContent = runnerParts.join(\" / \");\n document.getElementById(\"progress-fill\").style.width = Math.max(0, Math.min(100, percentage)) + \"%\";\n document.getElementById(\"progress-label\").textContent =\n percentage + \"% \" + (progress.sessionDone || 0) + \"/\" + (progress.sessionTotal || 0);\n document.getElementById(\"run-current\").textContent = runState.currentPromptLabel\n ? runState.currentPromptLabel + \" - attempt \" + runState.currentAttempt + \"/\" + runState.maxAttempts\n : \"No active prompt\";\n\n renderRunOutput(runState);\n }\n\n function renderRunOutput(runState) {\n const statusLines = (runState.agentStatusTableRows || []).map((row) =>\n row.status + \" - \" + row.agentName + (row.url ? \" - \" + row.url : \"\")\n );\n const outputLines = [\n ...(runState.agentStatusLines || []),\n ...statusLines,\n ...(runState.agentOutputLines || []),\n ...(runState.errors || []).map((errorLine) => \"Error: \" + errorLine),\n ].slice(-3);\n\n document.getElementById(\"run-output\").textContent = outputLines.join(\"\\n\");\n }\n\n async function fetchPrompts() {\n try {\n const response = await fetch(\"/api/prompts\");\n if (!response.ok) {\n showError(\"Prompts API error: \" + response.status);\n return;\n }\n\n renderBoard(await response.json());\n } catch (error) {\n showError(\"Could not load prompts: \" + error.message);\n }\n }\n\n function renderBoard(promptFiles) {\n const columns = Object.fromEntries(BOARD_COLUMNS.map((column) => [column.id, []]));\n\n for (const file of promptFiles) {\n for (const section of file.sections) {\n if (columns[section.column]) {\n columns[section.column].push({ file, section });\n }\n }\n }\n\n for (const column of BOARD_COLUMNS) {\n renderColumn(column.id, columns[column.id]);\n }\n }\n\n function renderColumn(columnId, cards) {\n const container = document.getElementById(\"cards-\" + columnId);\n const countElement = document.getElementById(\"count-\" + columnId);\n\n countElement.textContent = cards.length;\n container.innerHTML = \"\";\n\n if (cards.length === 0) {\n container.innerHTML = '<div class=\"empty-column\">Empty</div>';\n return;\n }\n\n for (const cardData of cards) {\n container.appendChild(createPromptCard(cardData.file, cardData.section));\n }\n }\n\n function createPromptCard(file, section) {\n const card = document.createElement(\"article\");\n card.className = \"card card-\" + section.column;\n card.innerHTML =\n '<div class=\"card-file\">' + escapeHtml(file.relativeFilePath || file.fileName) + \" #\" + (section.index + 1) + '</div>' +\n '<div class=\"card-summary\">' + escapeHtml(section.summary) + '</div>' +\n renderTags(section);\n card.onclick = () => openModal(file, section);\n return card;\n }\n\n function renderTags(section) {\n const tags = [...(section.tags || [])];\n if (section.priority > 0) {\n tags.push({ id: \"priority\", label: \"P\" + section.priority });\n }\n\n if (tags.length === 0) {\n return \"\";\n }\n\n return '<div class=\"card-tags\">' + tags.map((tag) =>\n '<span class=\"card-tag tag-' + escapeHtml(tag.id) + '\">' + escapeHtml(tag.label) + '</span>'\n ).join(\"\") + '</div>';\n }\n\n function openModal(file, section) {\n modalState = { filePath: file.filePath, sectionIndex: section.index };\n\n document.getElementById(\"modal-file\").textContent = file.relativeFilePath || file.fileName;\n document.getElementById(\"modal-section-label\").textContent = \"Section \" + (section.index + 1);\n\n const statusBadge = document.getElementById(\"modal-status-badge\");\n statusBadge.textContent = getColumnTitle(section.column);\n statusBadge.className = \"modal-status status-\" + section.column;\n\n document.getElementById(\"modal-content\").value = section.content;\n document.getElementById(\"modal-overlay\").classList.remove(\"hidden\");\n setTimeout(() => document.getElementById(\"modal-content\").focus(), 50);\n }\n\n function closeModal() {\n document.getElementById(\"modal-overlay\").classList.add(\"hidden\");\n modalState = null;\n }\n\n async function saveModal() {\n if (!modalState) {\n return;\n }\n\n const content = document.getElementById(\"modal-content\").value;\n const saveButton = document.getElementById(\"save-button\");\n saveButton.disabled = true;\n saveButton.textContent = \"Saving...\";\n\n try {\n const response = await fetch(\"/api/prompts/update\", {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n filePath: modalState.filePath,\n sectionIndex: modalState.sectionIndex,\n content,\n }),\n });\n\n if (!response.ok) {\n throw new Error(\"HTTP \" + response.status);\n }\n\n closeModal();\n await fetchPrompts();\n } catch (error) {\n showError(\"Save failed: \" + error.message);\n } finally {\n saveButton.disabled = false;\n saveButton.textContent = \"Save\";\n }\n }\n\n document.getElementById(\"toggle-btn\").onclick = async () => {\n try {\n const endpoint = lastPauseState === \"RUNNING\" ? \"/api/pause\" : \"/api/resume\";\n await fetch(endpoint, { method: \"POST\" });\n await fetchStatus();\n } catch (error) {\n showError(\"Toggle failed: \" + error.message);\n }\n };\n\n document.getElementById(\"modal-close\").onclick = closeModal;\n document.getElementById(\"cancel-button\").onclick = closeModal;\n document.getElementById(\"save-button\").onclick = saveModal;\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n closeModal();\n return;\n }\n if ((event.ctrlKey || event.metaKey) && event.key === \"Enter\") {\n saveModal();\n }\n });\n\n document.getElementById(\"modal-overlay\").addEventListener(\"click\", (event) => {\n if (event.target === document.getElementById(\"modal-overlay\")) {\n closeModal();\n }\n });\n\n renderBoardSkeleton();\n fetchStatus();\n fetchPrompts();\n setInterval(fetchStatus, 2000);\n setInterval(fetchPrompts, 5000);\n </script>\n</body>\n</html>\n";
9
+ export declare const CODER_SERVER_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Ptbk Coder Server</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n background: #f6f7f9;\n color: #1f2933;\n min-height: 100vh;\n }\n\n header {\n background: #22313f;\n color: white;\n padding: 12px 18px;\n display: flex;\n align-items: center;\n gap: 12px;\n position: sticky;\n top: 0;\n z-index: 10;\n box-shadow: 0 2px 10px rgba(15,23,42,0.22);\n }\n header h1 { font-size: 17px; font-weight: 700; flex-shrink: 0; letter-spacing: 0; }\n\n .status-badge {\n padding: 3px 10px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n flex-shrink: 0;\n }\n .status-RUNNING { background: #0b875b; color: white; }\n .status-PAUSING { background: #d97904; color: white; }\n .status-PAUSED { background: #b42318; color: white; }\n\n #pause-label {\n font-size: 12px;\n color: rgba(255,255,255,0.75);\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .btn {\n padding: 6px 12px;\n border: 0;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 700;\n transition: background 0.15s, opacity 0.15s;\n flex-shrink: 0;\n }\n .btn:disabled { opacity: 0.55; cursor: default; }\n .btn-pause { background: #ffd166; color: #1f2933; }\n .btn-resume { background: #0b875b; color: white; }\n .btn-save { background: #2563eb; color: white; }\n .btn-cancel { background: #edf0f4; color: #1f2933; }\n\n .run-strip {\n background: white;\n border-bottom: 1px solid #d8dee8;\n padding: 12px 18px;\n display: grid;\n grid-template-columns: minmax(220px, 1.2fr) minmax(240px, 1fr) minmax(260px, 1.4fr);\n gap: 14px;\n align-items: center;\n }\n .run-title {\n font-size: 14px;\n font-weight: 700;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .run-subtitle,\n .run-current,\n .run-output {\n color: #64748b;\n font-size: 12px;\n line-height: 1.45;\n overflow: hidden;\n }\n .run-current,\n .run-output {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n }\n .progress-shell {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 8px;\n align-items: center;\n }\n .progress-track {\n height: 10px;\n background: #e4e9f0;\n border-radius: 999px;\n overflow: hidden;\n }\n .progress-fill {\n display: block;\n height: 100%;\n width: 0;\n background: linear-gradient(90deg, #0b875b, #2563eb);\n transition: width 0.2s;\n }\n .progress-label {\n color: #475569;\n font-size: 12px;\n font-weight: 700;\n min-width: 72px;\n text-align: right;\n }\n\n .board {\n display: flex;\n gap: 14px;\n padding: 16px;\n overflow-x: auto;\n min-height: calc(100vh - 119px);\n align-items: flex-start;\n }\n\n .column {\n background: #e9edf3;\n border-top: 4px solid #94a3b8;\n border-radius: 6px;\n padding: 10px;\n min-width: 250px;\n width: 270px;\n flex-shrink: 0;\n }\n .column-backlog { border-top-color: #64748b; }\n .column-low-priority { border-top-color: #d97904; }\n .column-todo { border-top-color: #2563eb; }\n .column-in-progress { border-top-color: #7c3aed; }\n .column-done { border-top-color: #0b875b; }\n .column-errors { border-top-color: #b42318; }\n .column-finished { border-top-color: #0891b2; }\n\n .column-header {\n font-size: 12px;\n font-weight: 800;\n text-transform: uppercase;\n color: #475569;\n margin-bottom: 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .column-count {\n background: rgba(255,255,255,0.72);\n border-radius: 999px;\n padding: 2px 8px;\n font-size: 11px;\n color: #475569;\n }\n .column-cards { min-height: 40px; }\n\n .card {\n background: white;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 8px;\n box-shadow: 0 1px 3px rgba(15,23,42,0.12);\n cursor: pointer;\n border-left: 3px solid #94a3b8;\n }\n .card:hover { box-shadow: 0 4px 12px rgba(15,23,42,0.16); }\n .card-backlog { border-left-color: #64748b; }\n .card-low-priority { border-left-color: #d97904; }\n .card-todo { border-left-color: #2563eb; }\n .card-in-progress { border-left-color: #7c3aed; }\n .card-done { border-left-color: #0b875b; }\n .card-errors { border-left-color: #b42318; }\n .card-finished { border-left-color: #0891b2; }\n\n .card-file {\n font-size: 10px;\n color: #7b8794;\n margin-bottom: 6px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .card-summary {\n font-size: 13px;\n line-height: 1.45;\n color: #1f2933;\n word-break: break-word;\n white-space: pre-wrap;\n max-height: 76px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 4;\n -webkit-box-orient: vertical;\n }\n .card-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n margin-top: 8px;\n }\n .card-tag {\n border-radius: 999px;\n padding: 2px 7px;\n font-size: 10px;\n font-weight: 800;\n line-height: 1.35;\n }\n .tag-not-ready { background: #e2e8f0; color: #475569; }\n .tag-left-in-progress { background: #ffe4e6; color: #9f1239; }\n .tag-unwritten { background: #fff4cc; color: #8a5a00; }\n .tag-implementing { background: #ede9fe; color: #5b21b6; }\n .tag-verifying { background: #fae8ff; color: #86198f; }\n .tag-priority { background: #ffedd5; color: #9a3412; }\n\n .empty-column {\n color: #94a3b8;\n font-size: 12px;\n padding: 10px 4px;\n text-align: center;\n }\n\n .modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(15,23,42,0.58);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 56px 16px 16px;\n z-index: 100;\n }\n .modal-overlay.hidden { display: none; }\n\n .modal {\n background: white;\n border-radius: 8px;\n width: 100%;\n max-width: 720px;\n padding: 18px;\n box-shadow: 0 14px 40px rgba(15,23,42,0.28);\n }\n .modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n margin-bottom: 14px;\n gap: 12px;\n }\n .modal-meta { flex: 1; min-width: 0; }\n .modal-file {\n font-size: 11px;\n color: #7b8794;\n margin-bottom: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .modal-section-label { font-size: 14px; font-weight: 700; color: #1f2933; }\n .modal-status {\n font-size: 11px;\n font-weight: 800;\n padding: 3px 8px;\n border-radius: 999px;\n text-transform: uppercase;\n flex-shrink: 0;\n }\n .status-backlog { background: #e2e8f0; color: #475569; }\n .status-low-priority { background: #ffedd5; color: #9a3412; }\n .status-todo { background: #dbeafe; color: #1d4ed8; }\n .status-in-progress { background: #ede9fe; color: #5b21b6; }\n .status-done { background: #dcfce7; color: #166534; }\n .status-errors { background: #fee2e2; color: #991b1b; }\n .status-finished { background: #cffafe; color: #155e75; }\n .modal-close {\n background: transparent;\n border: 0;\n color: #64748b;\n cursor: pointer;\n font-size: 20px;\n padding: 0 4px;\n line-height: 1;\n }\n .modal-close:hover { color: #1f2933; }\n\n textarea {\n width: 100%;\n min-height: 300px;\n font-family: \"SFMono-Regular\", Consolas, \"Courier New\", monospace;\n font-size: 13px;\n border: 2px solid #d8dee8;\n border-radius: 4px;\n padding: 10px 12px;\n resize: vertical;\n line-height: 1.6;\n color: #1f2933;\n }\n textarea:focus {\n outline: none;\n border-color: #2563eb;\n box-shadow: 0 0 0 3px rgba(37,99,235,0.12);\n }\n .modal-actions {\n display: flex;\n gap: 8px;\n margin-top: 14px;\n justify-content: flex-end;\n }\n\n .error-banner {\n background: #fee2e2;\n border-bottom: 2px solid #b42318;\n color: #991b1b;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 700;\n }\n .error-banner.hidden { display: none; }\n\n @media (max-width: 860px) {\n header { flex-wrap: wrap; }\n #pause-label { order: 4; flex-basis: 100%; }\n .run-strip { grid-template-columns: 1fr; }\n .board { min-height: calc(100vh - 212px); }\n }\n </style>\n</head>\n<body>\n <div id=\"error-banner\" class=\"error-banner hidden\"></div>\n\n <header>\n <h1>Ptbk Coder Server</h1>\n <span id=\"status-badge\" class=\"status-badge status-RUNNING\">RUNNING</span>\n <span id=\"pause-label\"></span>\n <button id=\"toggle-btn\" class=\"btn btn-pause\">Pause</button>\n </header>\n\n <section class=\"run-strip\">\n <div>\n <div class=\"run-title\" id=\"run-title\">Initializing...</div>\n <div class=\"run-subtitle\" id=\"run-subtitle\">Loading runner state</div>\n </div>\n <div class=\"progress-shell\">\n <div class=\"progress-track\"><span class=\"progress-fill\" id=\"progress-fill\"></span></div>\n <div class=\"progress-label\" id=\"progress-label\">0%</div>\n </div>\n <div>\n <div class=\"run-current\" id=\"run-current\">No active prompt</div>\n <div class=\"run-output\" id=\"run-output\"></div>\n </div>\n </section>\n\n <div class=\"board\" id=\"board\"></div>\n\n <div class=\"modal-overlay hidden\" id=\"modal-overlay\">\n <div class=\"modal\">\n <div class=\"modal-header\">\n <div class=\"modal-meta\">\n <div class=\"modal-file\" id=\"modal-file\"></div>\n <div class=\"modal-section-label\" id=\"modal-section-label\"></div>\n </div>\n <span class=\"modal-status\" id=\"modal-status-badge\"></span>\n <button class=\"modal-close\" id=\"modal-close\" title=\"Close\">&times;</button>\n </div>\n\n <textarea id=\"modal-content\" placeholder=\"Prompt content\"></textarea>\n\n <div class=\"modal-actions\">\n <button class=\"btn btn-cancel\" id=\"cancel-button\">Cancel</button>\n <button class=\"btn btn-save\" id=\"save-button\">Save</button>\n </div>\n </div>\n </div>\n\n <script>\n \"use strict\";\n\n const BOARD_COLUMNS = [\n { id: \"backlog\", title: \"Backlog\" },\n { id: \"low-priority\", title: \"Outside priority\" },\n { id: \"todo\", title: \"To do\" },\n { id: \"in-progress\", title: \"In progress\" },\n { id: \"done\", title: \"Done\" },\n { id: \"errors\", title: \"Errors\" },\n { id: \"finished\", title: \"Finished\" },\n ];\n\n let modalState = null;\n let lastPauseState = \"RUNNING\";\n\n function getColumnTitle(columnId) {\n const column = BOARD_COLUMNS.find((columnCandidate) => columnCandidate.id === columnId);\n return column ? column.title : columnId;\n }\n\n function showError(message) {\n const banner = document.getElementById(\"error-banner\");\n banner.textContent = message;\n banner.classList.remove(\"hidden\");\n clearTimeout(banner.timer);\n banner.timer = setTimeout(() => banner.classList.add(\"hidden\"), 6000);\n }\n\n function escapeHtml(value) {\n const element = document.createElement(\"div\");\n element.textContent = String(value);\n return element.innerHTML;\n }\n\n function renderBoardSkeleton() {\n const board = document.getElementById(\"board\");\n board.innerHTML = \"\";\n\n for (const column of BOARD_COLUMNS) {\n const columnElement = document.createElement(\"section\");\n columnElement.className = \"column column-\" + column.id;\n columnElement.innerHTML =\n '<div class=\"column-header\">' +\n '<span>' + escapeHtml(column.title) + '</span>' +\n '<span class=\"column-count\" id=\"count-' + column.id + '\">0</span>' +\n '</div>' +\n '<div class=\"column-cards\" id=\"cards-' + column.id + '\">' +\n '<div class=\"empty-column\">Loading</div>' +\n '</div>';\n board.appendChild(columnElement);\n }\n }\n\n async function fetchStatus() {\n try {\n const response = await fetch(\"/api/status\");\n if (!response.ok) {\n showError(\"Status API error: \" + response.status);\n return;\n }\n\n const status = await response.json();\n renderPauseState(status);\n renderRunState(status.runState);\n } catch (error) {\n showError(\"Could not reach coder server: \" + error.message);\n }\n }\n\n function renderPauseState(status) {\n lastPauseState = status.pauseState;\n\n const badge = document.getElementById(\"status-badge\");\n badge.textContent = status.pauseState;\n badge.className = \"status-badge status-\" + status.pauseState;\n\n const toggleButton = document.getElementById(\"toggle-btn\");\n const pauseLabel = document.getElementById(\"pause-label\");\n\n if (status.pauseState === \"RUNNING\") {\n toggleButton.textContent = \"Pause\";\n toggleButton.className = \"btn btn-pause\";\n pauseLabel.textContent = \"\";\n return;\n }\n\n toggleButton.textContent = \"Resume\";\n toggleButton.className = \"btn btn-resume\";\n pauseLabel.textContent =\n (status.pauseState === \"PAUSING\" ? \"Pausing before: \" : \"Paused before: \") +\n (status.pauseTargetLabel || \"next checkpoint\");\n }\n\n function renderRunState(runState) {\n if (!runState) {\n document.getElementById(\"run-title\").textContent = \"Waiting for runner state\";\n document.getElementById(\"run-subtitle\").textContent = \"\";\n return;\n }\n\n const progress = runState.progress || {};\n const percentage = Number(progress.percentage || 0);\n const runnerParts = [runState.config.agentName, runState.config.modelName, runState.config.thinkingLevel]\n .filter(Boolean);\n\n document.getElementById(\"run-title\").textContent = runState.statusMessage || runState.phase;\n document.getElementById(\"run-subtitle\").textContent = runnerParts.join(\" / \");\n document.getElementById(\"progress-fill\").style.width = Math.max(0, Math.min(100, percentage)) + \"%\";\n document.getElementById(\"progress-label\").textContent =\n percentage + \"% \" + (progress.sessionDone || 0) + \"/\" + (progress.sessionTotal || 0);\n document.getElementById(\"run-current\").textContent = runState.currentPromptLabel\n ? runState.currentPromptLabel + \" - attempt \" + runState.currentAttempt + \"/\" + runState.maxAttempts\n : \"No active prompt\";\n\n renderRunOutput(runState);\n }\n\n function renderRunOutput(runState) {\n const statusLines = (runState.agentStatusTableRows || []).map((row) =>\n row.status + \" - \" + row.agentName + (row.url ? \" - \" + row.url : \"\")\n );\n const outputLines = [\n ...(runState.agentStatusLines || []),\n ...statusLines,\n ...(runState.agentOutputLines || []),\n ...(runState.errors || []).map((errorLine) => \"Error: \" + errorLine),\n ].slice(-3);\n\n document.getElementById(\"run-output\").textContent = outputLines.join(\"\\n\");\n }\n\n async function fetchPrompts() {\n try {\n const response = await fetch(\"/api/prompts\");\n if (!response.ok) {\n showError(\"Prompts API error: \" + response.status);\n return;\n }\n\n renderBoard(await response.json());\n } catch (error) {\n showError(\"Could not load prompts: \" + error.message);\n }\n }\n\n function renderBoard(promptFiles) {\n const columns = Object.fromEntries(BOARD_COLUMNS.map((column) => [column.id, []]));\n\n for (const file of promptFiles) {\n for (const section of file.sections) {\n if (columns[section.column]) {\n columns[section.column].push({ file, section });\n }\n }\n }\n\n for (const column of BOARD_COLUMNS) {\n renderColumn(column.id, columns[column.id]);\n }\n }\n\n function renderColumn(columnId, cards) {\n const container = document.getElementById(\"cards-\" + columnId);\n const countElement = document.getElementById(\"count-\" + columnId);\n\n countElement.textContent = cards.length;\n container.innerHTML = \"\";\n\n if (cards.length === 0) {\n container.innerHTML = '<div class=\"empty-column\">Empty</div>';\n return;\n }\n\n for (const cardData of cards) {\n container.appendChild(createPromptCard(cardData.file, cardData.section));\n }\n }\n\n function createPromptCard(file, section) {\n const card = document.createElement(\"article\");\n card.className = \"card card-\" + section.column;\n card.innerHTML =\n '<div class=\"card-file\">' + escapeHtml(file.relativeFilePath || file.fileName) + \" #\" + (section.index + 1) + '</div>' +\n '<div class=\"card-summary\">' + escapeHtml(section.summary) + '</div>' +\n renderTags(section);\n card.onclick = () => openModal(file, section);\n return card;\n }\n\n function renderTags(section) {\n const tags = [...(section.tags || [])];\n if (section.priority > 0) {\n tags.push({ id: \"priority\", label: \"P\" + section.priority });\n }\n\n if (tags.length === 0) {\n return \"\";\n }\n\n return '<div class=\"card-tags\">' + tags.map((tag) =>\n '<span class=\"card-tag tag-' + escapeHtml(tag.id) + '\">' + escapeHtml(tag.label) + '</span>'\n ).join(\"\") + '</div>';\n }\n\n function openModal(file, section) {\n modalState = { filePath: file.filePath, sectionIndex: section.index };\n\n document.getElementById(\"modal-file\").textContent = file.relativeFilePath || file.fileName;\n document.getElementById(\"modal-section-label\").textContent = \"Section \" + (section.index + 1);\n\n const statusBadge = document.getElementById(\"modal-status-badge\");\n statusBadge.textContent = getColumnTitle(section.column);\n statusBadge.className = \"modal-status status-\" + section.column;\n\n document.getElementById(\"modal-content\").value = section.content;\n document.getElementById(\"modal-overlay\").classList.remove(\"hidden\");\n setTimeout(() => document.getElementById(\"modal-content\").focus(), 50);\n }\n\n function closeModal() {\n document.getElementById(\"modal-overlay\").classList.add(\"hidden\");\n modalState = null;\n }\n\n async function saveModal() {\n if (!modalState) {\n return;\n }\n\n const content = document.getElementById(\"modal-content\").value;\n const saveButton = document.getElementById(\"save-button\");\n saveButton.disabled = true;\n saveButton.textContent = \"Saving...\";\n\n try {\n const response = await fetch(\"/api/prompts/update\", {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n filePath: modalState.filePath,\n sectionIndex: modalState.sectionIndex,\n content,\n }),\n });\n\n if (!response.ok) {\n throw new Error(\"HTTP \" + response.status);\n }\n\n closeModal();\n await fetchPrompts();\n } catch (error) {\n showError(\"Save failed: \" + error.message);\n } finally {\n saveButton.disabled = false;\n saveButton.textContent = \"Save\";\n }\n }\n\n document.getElementById(\"toggle-btn\").onclick = async () => {\n try {\n const endpoint = lastPauseState === \"RUNNING\" ? \"/api/pause\" : \"/api/resume\";\n await fetch(endpoint, { method: \"POST\" });\n await fetchStatus();\n } catch (error) {\n showError(\"Toggle failed: \" + error.message);\n }\n };\n\n document.getElementById(\"modal-close\").onclick = closeModal;\n document.getElementById(\"cancel-button\").onclick = closeModal;\n document.getElementById(\"save-button\").onclick = saveModal;\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n closeModal();\n return;\n }\n if ((event.ctrlKey || event.metaKey) && event.key === \"Enter\") {\n saveModal();\n }\n });\n\n document.getElementById(\"modal-overlay\").addEventListener(\"click\", (event) => {\n if (event.target === document.getElementById(\"modal-overlay\")) {\n closeModal();\n }\n });\n\n renderBoardSkeleton();\n fetchStatus();\n fetchPrompts();\n setInterval(fetchStatus, 2000);\n setInterval(fetchPrompts, 5000);\n </script>\n</body>\n</html>\n";
@@ -2,7 +2,7 @@
2
2
  * Overwrites the body of one prompt section with new content, preserving the status line.
3
3
  *
4
4
  * The `newContent` string is the prompt text without the status marker.
5
- * The status line (`[ ]`, `[x]`, `[!]`, `[-]`) is kept intact.
5
+ * The status line (`[ ]`, `[^]`, `[x]`, `[!]`, `[-]`) is kept intact.
6
6
  *
7
7
  * @private internal utility of `ptbk coder server`
8
8
  */
@@ -1,4 +1,5 @@
1
1
  import type { CoderRunStep } from '../common/CoderRunStep';
2
+ import { type OnCoderRunStepStarted } from '../common/createCoderRunStepTracker';
2
3
  import type { PromptRunOptions } from '../runners/types/PromptRunOptions';
3
4
  import type { PromptRunResult } from '../runners/types/PromptRunResult';
4
5
  import type { PromptRunner } from '../runners/types/PromptRunner';
@@ -11,6 +12,11 @@ type RunPromptWithTestFeedbackOptions = PromptRunOptions & {
11
12
  promptLabel: string;
12
13
  testCommand?: string;
13
14
  onAttemptStarted?: (attemptCount: number) => void;
15
+ /**
16
+ * Notified right before each implementation, testing and fixing step starts, so the caller can
17
+ * record the in-progress state of the prompt.
18
+ */
19
+ onStepStarted?: OnCoderRunStepStarted;
14
20
  runPromptTestCommandExecutor?: typeof runPromptTestCommand;
15
21
  };
16
22
  /**
@@ -0,0 +1,9 @@
1
+ import type { PromptFile } from '../run-codex-prompts/prompts/types/PromptFile';
2
+ import type { VerifyPromptsOrder } from './VerifyPromptsOrder';
3
+ /**
4
+ * Orders the loaded prompt files for one verification pass.
5
+ *
6
+ * Note: `$` is used to indicate that this function is not a pure function - the `random` order is not deterministic
7
+ * Note: This function does NOT mutate the given array
8
+ */
9
+ export declare function $orderPromptFiles(promptFiles: ReadonlyArray<PromptFile>, order: VerifyPromptsOrder): PromptFile[];
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Orders supported by `ptbk coder verify --order`.
3
+ */
4
+ export declare const VERIFY_PROMPTS_ORDER_VALUES: readonly ["from-earliest", "from-latest", "random"];
5
+ /**
6
+ * Order in which the prompt files are processed during one verification run.
7
+ */
8
+ export type VerifyPromptsOrder = (typeof VERIFY_PROMPTS_ORDER_VALUES)[number];
9
+ /**
10
+ * Order used when `--order` is not provided.
11
+ */
12
+ export declare const DEFAULT_VERIFY_PROMPTS_ORDER: VerifyPromptsOrder;
13
+ /**
14
+ * Human-readable description of each supported order, shared by the CLI help and the verification output.
15
+ */
16
+ export declare const VERIFY_PROMPTS_ORDER_DESCRIPTIONS: {
17
+ readonly 'from-earliest': "from the earliest prompt file";
18
+ readonly 'from-latest': "from the latest prompt file";
19
+ readonly random: "in random order";
20
+ };
21
+ /**
22
+ * Parses and validates one raw `--order` value.
23
+ *
24
+ * Note: `ptbk coder verify` lets Commander validate the value, this is used by the standalone script which parses the raw arguments itself
25
+ */
26
+ export declare function parseVerifyPromptsOrder(orderValue: string | undefined): VerifyPromptsOrder;
@@ -1,13 +1,14 @@
1
1
  import type { CoderGitSyncOptions } from '../run-codex-prompts/git/coderGitSync';
2
2
  import type { PromptFile } from '../run-codex-prompts/prompts/types/PromptFile';
3
+ import type { VerifyPromptsOrder } from './VerifyPromptsOrder';
3
4
  /**
4
5
  * Options supported by the prompt verification helper.
5
6
  */
6
7
  export type VerifyPromptsOptions = {
7
8
  /**
8
- * Process prompt files in reverse order.
9
+ * Order in which the prompt files are processed.
9
10
  */
10
- readonly reverse?: boolean;
11
+ readonly order?: VerifyPromptsOrder;
11
12
  /**
12
13
  * Ignore prompt files whose filename or first prompt line contains one of the provided values.
13
14
  */
@@ -1,4 +1,4 @@
1
- import type { Command as Program } from 'commander';
1
+ import { Command as Program } from 'commander';
2
2
  import type { $side_effect } from '../../../utils/organization/$side_effect';
3
3
  /**
4
4
  * Initializes `coder verify` command for Promptbook CLI utilities
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.114.0-3`).
18
+ * It follows semantic versioning (e.g., `0.114.0-4`).
19
19
  *
20
20
  * @generated
21
21
  */
@@ -1,7 +0,0 @@
1
- /**
2
- * Replaces the complete todo status line while preserving its indentation.
3
- *
4
- * The complete line is replaced because a todo status can contain a required
5
- * model/harness token in addition to priority markers.
6
- */
7
- export declare function replacePromptTodoStatusLine(line: string, replacementStatusLine: string): string;
@@ -1,7 +0,0 @@
1
- /**
2
- * Replaces the complete todo status line while preserving its indentation.
3
- *
4
- * The complete line is replaced because a todo status can contain a required
5
- * model/harness token in addition to priority markers.
6
- */
7
- export declare function replacePromptTodoStatusLine(line: string, replacementStatusLine: string): string;