@jskit-ai/jskit-cli 0.2.82 → 0.2.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.82",
3
+ "version": "0.2.84",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,9 +20,9 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.81",
24
- "@jskit-ai/kernel": "0.1.73",
25
- "@jskit-ai/shell-web": "0.1.72",
23
+ "@jskit-ai/jskit-catalog": "0.1.83",
24
+ "@jskit-ai/kernel": "0.1.75",
25
+ "@jskit-ai/shell-web": "0.1.74",
26
26
  "@vue/compiler-sfc": "^3.5.29",
27
27
  "ts-morph": "^28.0.0"
28
28
  },
@@ -280,9 +280,25 @@ async function resolveStepInputs({
280
280
  return closeReason;
281
281
  }
282
282
 
283
+ const codexResult = await resolveTextInput({
284
+ codePrefix: "codex_result",
285
+ fileOption: "codex-result-file",
286
+ inlineOptions,
287
+ io,
288
+ repairCommand: `jskit session ${sessionId} step --codex-result -`,
289
+ cwd,
290
+ sessionId,
291
+ stdinOption: "-",
292
+ textOption: "codex-result"
293
+ });
294
+ if (codexResult.ok === false) {
295
+ return codexResult;
296
+ }
297
+
283
298
  return {
284
299
  agentDecisions: agentDecisions.value,
285
300
  closeReason: closeReason.value,
301
+ codexResult: codexResult.value,
286
302
  issue: issue.value,
287
303
  issueTitle: issueTitle.value,
288
304
  ok: true,
@@ -365,7 +381,8 @@ function createSessionCommands() {
365
381
  reworkNotes: stepInputs.reworkNotes,
366
382
  skipReason: stepInputs.skipReason,
367
383
  agentDecisions: stepInputs.agentDecisions,
368
- closeReason: stepInputs.closeReason
384
+ closeReason: stepInputs.closeReason,
385
+ codexResult: stepInputs.codexResult
369
386
  }
370
387
  });
371
388
  } else if (second === "abandon") {
@@ -226,6 +226,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
226
226
  "Use --issue-title when the approved issue title is separate from the body.",
227
227
  "Use --issue-details - to read confirmed issue details from stdin.",
228
228
  "Use --plan - to read the approved implementation plan from stdin.",
229
+ "Use --codex-result - after Codex prompt steps to read the final marked Codex result from stdin.",
229
230
  "Use --rework-notes - with --user-check failed to start the next plan cycle.",
230
231
  "Use --agent-decisions - to append session-local decision log entries from implementation, UI review, verification, or repair phases.",
231
232
  "Use --review-findings-remaining true --review-findings \"<findings>\" when an accepted review pass needs another pass.",
@@ -245,6 +246,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
245
246
  "jskit session 2026-05-11_21-42-08 step --issue-title \"Fix customer filters\" --issue -",
246
247
  "jskit session 2026-05-11_21-42-08 step --issue-details -",
247
248
  "jskit session 2026-05-11_21-42-08 step --plan -",
249
+ "jskit session 2026-05-11_21-42-08 step --codex-result -",
248
250
  "jskit session 2026-05-11_21-42-08 step --agent-decisions -",
249
251
  "jskit session 2026-05-11_21-42-08 step --review-findings-remaining true --review-findings \"A helper duplication fix needs another pass\"",
250
252
  "jskit session 2026-05-11_21-42-08 step --review-findings-remaining false",
@@ -258,7 +260,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
258
260
  })
259
261
  ]),
260
262
  fullUse:
261
- "jskit session [create|<sessionId>] [step|diff|abandon|adopt-codex-thread] [--prompt <text>] [--issue-title <text>|--issue-title-file <path>] [--issue <text>|--issue-file <path>] [--issue-details <text>|--issue-details-file <path>] [--plan <text>|--plan-file <path>] [--agent-decisions <text>|--agent-decisions-file <path>] [--review-findings-remaining true --review-findings <text>|--review-findings-remaining false] [--skip-ui-check --skip-reason <text>] [--merge-pr true|--close-without-merge --close-reason <text>] [--user-check <passed|failed>] [--rework-notes <text>|--rework-notes-file <path>] [--codex-thread-id <id>] [--abandoned|--completed|--all] [--json]",
263
+ "jskit session [create|<sessionId>] [step|diff|abandon|adopt-codex-thread] [--prompt <text>] [--issue-title <text>|--issue-title-file <path>] [--issue <text>|--issue-file <path>] [--issue-details <text>|--issue-details-file <path>] [--plan <text>|--plan-file <path>] [--codex-result <text>|--codex-result-file <path>] [--agent-decisions <text>|--agent-decisions-file <path>] [--review-findings-remaining true --review-findings <text>|--review-findings-remaining false] [--skip-ui-check --skip-reason <text>] [--merge-pr true|--close-without-merge --close-reason <text>] [--user-check <passed|failed>] [--rework-notes <text>|--rework-notes-file <path>] [--codex-thread-id <id>] [--abandoned|--completed|--all] [--json]",
262
264
  showHelpOnBareInvocation: false,
263
265
  handlerName: "commandSession",
264
266
  allowedFlagKeys: Object.freeze(["json", "abandoned", "completed", "all"]),
@@ -138,24 +138,100 @@ const USER_CHECK_INPUT = Object.freeze({
138
138
  required: true,
139
139
  type: "choice"
140
140
  });
141
- function codexHandoff(expectedOutput, {
141
+
142
+ const JSKIT_STEP_RESULT_CONTRACT = Object.freeze({
143
+ completionBehavior: "auto_advance",
144
+ kind: "completion_marker",
145
+ marker: "jskit_step_result",
146
+ missingMarkerBehavior: "resend",
147
+ required: true,
148
+ stepField: "step"
149
+ });
150
+ const DESLOP_RESULT_CONTRACT = Object.freeze({
151
+ autoResolvePriorities: Object.freeze(["high", "medium"]),
152
+ completionBehavior: "deslop_loop",
153
+ kind: "deslop_result",
154
+ marker: "deslop_result",
155
+ missingMarkerBehavior: "resend",
156
+ required: true,
157
+ resolvePrompt: Object.freeze({
158
+ findingsPlaceholder: "{{findings}}",
159
+ marker: "resolve_deslop_findings",
160
+ templateFile: "resolve_deslop_findings.md"
161
+ })
162
+ });
163
+
164
+ function fieldResponseContract(outputDefinitions) {
165
+ const fields = outputDefinitions
166
+ .filter((output) => output?.field && output?.extract)
167
+ .map((output) => Object.freeze({
168
+ extract: output.extract,
169
+ field: output.field,
170
+ formatHint: output.formatHint || "",
171
+ label: output.label || output.field,
172
+ manualEntry: true,
173
+ ...(output.multiline === true ? { multiline: true } : {}),
174
+ ...(Array.isArray(output.options) ? { options: Object.freeze(output.options.map((option) => Object.freeze({ ...option }))) } : {}),
175
+ required: output.required !== false
176
+ }));
177
+ return Object.freeze({
178
+ fields: Object.freeze(fields),
179
+ kind: "fields",
180
+ missingMarkerBehavior: "manual_or_resend",
181
+ required: fields.some((field) => field.required)
182
+ });
183
+ }
184
+
185
+ function codexHandoff(outputDefinition, {
142
186
  autoInject = false,
143
- promptActionLabel = ""
187
+ promptActionLabel = "",
188
+ promptIntroText = "",
189
+ promptWaitingText = "",
190
+ responseContract = undefined
144
191
  } = {}) {
145
- const expectedOutputs = Object.freeze(Array.isArray(expectedOutput) ? [...expectedOutput] : [expectedOutput]);
192
+ const outputDefinitions = Object.freeze(Array.isArray(outputDefinition) ? [...outputDefinition] : [outputDefinition]);
193
+ const resolvedResponseContract = responseContract === undefined
194
+ ? fieldResponseContract(outputDefinitions)
195
+ : responseContract;
146
196
  return Object.freeze({
147
197
  ...(autoInject ? { autoInject: true } : {}),
148
- expectedOutput: expectedOutputs[expectedOutputs.length - 1] || null,
149
- expectedOutputs,
150
198
  mode: "inject_prompt",
151
199
  promptField: "prompt",
152
- ...(promptActionLabel ? { promptActionLabel } : {})
200
+ ...(promptActionLabel ? { promptActionLabel } : {}),
201
+ ...(promptIntroText ? { promptIntroText } : {}),
202
+ ...(promptWaitingText ? { promptWaitingText } : {}),
203
+ ...(resolvedResponseContract ? { responseContract: resolvedResponseContract } : {})
153
204
  });
154
205
  }
155
206
 
207
+ function stepAutomationFor({
208
+ codex = undefined,
209
+ id,
210
+ kind,
211
+ requiresExplicitRun = false
212
+ }) {
213
+ if (requiresExplicitRun) {
214
+ return Object.freeze({ mode: "manual" });
215
+ }
216
+ if (id === "dependencies_installed") {
217
+ return Object.freeze({ mode: "terminal" });
218
+ }
219
+ if (kind === "automatic") {
220
+ return Object.freeze({ mode: "immediate" });
221
+ }
222
+ if (kind === "codex_prompt" && codex?.autoInject === true) {
223
+ return Object.freeze({ mode: "codex_prompt" });
224
+ }
225
+ if (kind === "codex_output" && codex?.autoInject === true) {
226
+ return Object.freeze({ mode: "codex_output_prompt" });
227
+ }
228
+ return Object.freeze({ mode: "manual" });
229
+ }
230
+
156
231
  const PLAN_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
157
232
  autoInject: true,
158
- promptActionLabel: "Get Codex to execute plan"
233
+ promptActionLabel: "Get Codex to execute plan",
234
+ responseContract: JSKIT_STEP_RESULT_CONTRACT
159
235
  });
160
236
  const ISSUE_DETAILS_CODEX_HANDOFF = codexHandoff([
161
237
  ISSUE_CATEGORY_OUTPUT,
@@ -163,23 +239,29 @@ const ISSUE_DETAILS_CODEX_HANDOFF = codexHandoff([
163
239
  ISSUE_DETAILS_OUTPUT
164
240
  ], {
165
241
  autoInject: true,
166
- promptActionLabel: "Start details conversation"
242
+ promptActionLabel: "Start details conversation",
243
+ promptIntroText: "Codex will gather issue details and classify the issue.",
244
+ promptWaitingText: "Please respond and finalise things with Codex"
167
245
  });
168
246
  const REVIEW_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
169
247
  autoInject: true,
170
- promptActionLabel: "Run deslop"
248
+ promptActionLabel: "Run deslop",
249
+ responseContract: DESLOP_RESULT_CONTRACT
171
250
  });
172
251
  const DEEP_UI_CHECK_CODEX_HANDOFF = codexHandoff([], {
173
252
  autoInject: true,
174
- promptActionLabel: "Run Deep UI check"
253
+ promptActionLabel: "Run Deep UI check",
254
+ responseContract: JSKIT_STEP_RESULT_CONTRACT
175
255
  });
176
256
  const AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF = codexHandoff([], {
177
257
  autoInject: true,
178
- promptActionLabel: "Run automated checks"
258
+ promptActionLabel: "Run automated checks",
259
+ responseContract: JSKIT_STEP_RESULT_CONTRACT
179
260
  });
180
261
  const BLUEPRINT_CODEX_HANDOFF = codexHandoff([], {
181
262
  autoInject: true,
182
- promptActionLabel: "Update blueprint"
263
+ promptActionLabel: "Update blueprint",
264
+ responseContract: JSKIT_STEP_RESULT_CONTRACT
183
265
  });
184
266
 
185
267
  function defineStep({
@@ -193,11 +275,20 @@ function defineStep({
193
275
  nextCommandTemplate = DEFAULT_NEXT_COMMAND_TEMPLATE,
194
276
  preconditions = [],
195
277
  requiresExplicitRun = false,
278
+ submitOptions = {},
279
+ automation = undefined,
196
280
  utilityActions = [],
197
281
  displayGroupId = "",
198
282
  displayGroupLabel = ""
199
283
  }) {
284
+ const resolvedAutomation = automation || stepAutomationFor({
285
+ codex,
286
+ id,
287
+ kind,
288
+ requiresExplicitRun
289
+ });
200
290
  return Object.freeze({
291
+ automation: Object.freeze({ ...resolvedAutomation }),
201
292
  buttonLabel,
202
293
  codex,
203
294
  description,
@@ -210,6 +301,7 @@ function defineStep({
210
301
  nextCommandTemplate,
211
302
  preconditions: Object.freeze([...preconditions]),
212
303
  requiresExplicitRun,
304
+ submitOptions: Object.freeze({ ...submitOptions }),
213
305
  utilityActions: Object.freeze([...utilityActions])
214
306
  });
215
307
  }
@@ -259,7 +351,8 @@ const STEP_DEFINITIONS = Object.freeze([
259
351
  ISSUE_TEXT_OUTPUT
260
352
  ], {
261
353
  autoInject: true,
262
- promptActionLabel: "Get Codex to create issue text"
354
+ promptActionLabel: "Get Codex to create issue text",
355
+ promptIntroText: "Codex will create a comprehensive issue."
263
356
  }),
264
357
  description: "Codex drafts the issue title and body; user reviews or edits them; JSKIT saves the approved draft.",
265
358
  id: "issue_drafted",
@@ -294,7 +387,8 @@ const STEP_DEFINITIONS = Object.freeze([
294
387
  buttonLabel: "Save plan",
295
388
  codex: codexHandoff(PLAN_OUTPUT, {
296
389
  autoInject: true,
297
- promptActionLabel: "Get Codex to create plan"
390
+ promptActionLabel: "Get Codex to create plan",
391
+ promptIntroText: "Codex will create an implementation plan based on the issue."
298
392
  }),
299
393
  description: "Codex writes an implementation plan for the active cycle; cycle 001 plans from the issue, later cycles plan from user rework notes.",
300
394
  id: "plan_made",
@@ -342,12 +436,15 @@ const STEP_DEFINITIONS = Object.freeze([
342
436
  kind: "user_check",
343
437
  label: "Review/deslop",
344
438
  nextCommandTemplate: `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} step --review-findings-remaining false`,
345
- preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "deep_ui_check_satisfied"]
439
+ preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "deep_ui_check_satisfied"],
440
+ submitOptions: Object.freeze({
441
+ reviewFindingsRemaining: false
442
+ })
346
443
  }),
347
444
  defineStep({
348
445
  buttonLabel: "Run automated checks",
349
446
  codex: AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF,
350
- description: "JSKIT asks Codex to run automated checks in the worktree, fix failures, and report the final result.",
447
+ description: "JSKIT asks Codex to run the official verification command in the worktree, fix failures, and report the final passing result.",
351
448
  id: "automated_checks_run",
352
449
  kind: "codex_prompt",
353
450
  label: "Automated checks",
@@ -361,7 +458,14 @@ const STEP_DEFINITIONS = Object.freeze([
361
458
  kind: "user_check",
362
459
  label: "User check",
363
460
  nextCommandTemplate: `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} step --user-check passed`,
364
- preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied"]
461
+ preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied"],
462
+ utilityActions: Object.freeze([
463
+ Object.freeze({
464
+ id: "session_app_test",
465
+ kind: "app_test",
466
+ label: "Test app"
467
+ })
468
+ ])
365
469
  }),
366
470
  defineStep({
367
471
  buttonLabel: "Commit accepted changes",
@@ -379,13 +483,6 @@ const STEP_DEFINITIONS = Object.freeze([
379
483
  label: "Blueprint updated",
380
484
  preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "accepted_changes_committed"]
381
485
  }),
382
- defineStep({
383
- buttonLabel: "Run verification",
384
- description: "JSKIT runs the final project verification command in the session worktree and records the result.",
385
- id: "doctor_run",
386
- label: "Verification run",
387
- preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "accepted_changes_committed", "blueprint_update_satisfied"]
388
- }),
389
486
  defineStep({
390
487
  buttonLabel: "Create final report",
391
488
  description: "JSKIT creates the deterministic final session report and comments it on the GitHub issue.",
@@ -406,7 +503,10 @@ const STEP_DEFINITIONS = Object.freeze([
406
503
  id: "pr_finalized",
407
504
  label: "PR finalized, worktree removed",
408
505
  preconditions: ["session_exists", "pr_url_exists", "worktree_exists"],
409
- requiresExplicitRun: true
506
+ requiresExplicitRun: true,
507
+ submitOptions: Object.freeze({
508
+ mergePr: true
509
+ })
410
510
  }),
411
511
  defineStep({
412
512
  buttonLabel: "Finish session",
@@ -32,7 +32,7 @@ Implementation rules:
32
32
  - Keep direct knex exceptional and minimal. Prefer internal json-rest-api seams outside generated CRUD packages and explicit weird-custom feature lanes.
33
33
  - Keep runtime, UI, and data concerns separated.
34
34
  - Avoid accidental scope expansion.
35
- - Do not create old workflow files such as `.jskit/WORKBOARD.md`; the session files and receipts are the workflow record.
35
+ - Do not create `.jskit/WORKBOARD.md`; the session files and receipts are the workflow record.
36
36
  - If user-facing UI changes, bring the screen to Material Design and Vuetify quality before calling it done. Include coherent responsive layout, loading, empty, error, disabled, and success states where relevant.
37
37
  - If verification needs login, use the app's local development auth bootstrap path rather than a live external auth login.
38
38
 
@@ -15,15 +15,9 @@ No stones unturned:
15
15
  - Read `.jskit/APP_BLUEPRINT.md` and `.jskit/helper-map.md` when present.
16
16
  - Read package.json, `.jskit/lock.json`, `config/public.js`, relevant `src/`, relevant `packages/`, and relevant package docs or `npx --no-install jskit show ... --details`.
17
17
  - Ask follow-up questions until all important issue details are known.
18
+ - Ask the user for whatever is still needed.
18
19
  - Ask for final confirmation before emitting final details.
19
-
20
- When you are ready to discuss details with the user, output this marker exactly once:
21
-
22
- [issue_details_conversation_ready]
23
- ready
24
- [/issue_details_conversation_ready]
25
-
26
- Then ask the user for whatever is still needed. Do not output the final markers below until the user has confirmed the details.
20
+ - Do not output the final markers below until the user has confirmed the details.
27
21
 
28
22
  For CRUD or persisted data, confirm entity/table name, fields, field types, nullability, defaults, uniqueness, indexes, relationships, ownership, allowed operations, list/view/form shape, validation, permissions, migration/generator lane, and exact CRUD generator command or exact reason no CRUD generator applies.
29
23
 
@@ -15,7 +15,7 @@ Do not run workflow, repair, or mutation commands during issue drafting:
15
15
 
16
16
  - Do not run `npx --no-install jskit session`, `npx --no-install jskit session step`, or any command that advances a JSKIT session.
17
17
  - Do not run `gh`, `git add`, `git commit`, `git push`, `npm install`, generators, tests, verification, devlinks, or doctor commands.
18
- - Do not try to fix missing PATH entries or missing command shims. If a tool is unavailable, continue with read-only file inspection.
18
+ - Do not try to fix missing PATH entries or missing helper binaries. If a tool is unavailable, continue with read-only file inspection.
19
19
  - Do not edit files.
20
20
 
21
21
  Draft an implementation-ready issue, not a broad product essay.
@@ -0,0 +1,16 @@
1
+ Resolve the listed deslop findings in the current worktree.
2
+
3
+ Fix only the listed findings. Keep scope tight. Do not create commits, branches, issues, pull requests, merges, or worktree cleanup.
4
+
5
+ Use existing JSKIT helpers, generators, package seams, and app-local helpers where they fit. Do not introduce helper churn or unrelated refactors while resolving these findings.
6
+
7
+ [resolve_deslop_findings]
8
+ {{findings}}
9
+ [/resolve_deslop_findings]
10
+
11
+ When finished:
12
+
13
+ - Summarize the exact findings fixed.
14
+ - List changed files.
15
+ - List checks run, or say why checks were not run.
16
+ - Wait for the next review prompt.
@@ -32,5 +32,7 @@ Rules:
32
32
  When finished, summarize the blueprint result normally, then end with this exact marker:
33
33
 
34
34
  [jskit_step_result]
35
- Blueprint updated.
35
+ status: complete
36
+ step: blueprint_updated
37
+ summary: Short summary of blueprint changes, or why no blueprint update was needed.
36
38
  [/jskit_step_result]
@@ -3,6 +3,8 @@ import path from "node:path";
3
3
  import {
4
4
  CYCLE_STEP_IDS,
5
5
  JSKIT_CLI_SHELL_COMMAND,
6
+ PROMPT_DIRECTORY,
7
+ REVIEW_EXECUTION_CODEX_HANDOFF,
6
8
  REVIEW_PASS_LIMIT,
7
9
  SESSION_WORKFLOW_VERSION,
8
10
  SESSION_STATUS,
@@ -73,8 +75,14 @@ function normalizeKnownStepIds(stepIds = []) {
73
75
  }
74
76
 
75
77
  function stepCanExposeStoredPrompt(stepId) {
76
- const step = STEP_DEFINITION_BY_ID[normalizeStepId(stepId)];
77
- return Boolean(step?.codex || step?.kind === "codex_prompt" || step?.kind === "human_input");
78
+ const normalizedStepId = normalizeStepId(stepId);
79
+ const step = STEP_DEFINITION_BY_ID[normalizedStepId];
80
+ return Boolean(
81
+ normalizedStepId === "review_changes_accepted" ||
82
+ step?.codex ||
83
+ step?.kind === "codex_prompt" ||
84
+ step?.kind === "human_input"
85
+ );
78
86
  }
79
87
 
80
88
  const DEFAULT_ACTIVE_CYCLE = "001";
@@ -198,6 +206,35 @@ async function readReviewPasses(paths) {
198
206
  return Promise.all(passes.map((pass) => readReviewPassInfo(paths, pass)));
199
207
  }
200
208
 
209
+ const REVIEW_STEP_IDS = Object.freeze([
210
+ "review_prompt_rendered",
211
+ "review_changes_accepted"
212
+ ]);
213
+
214
+ function latestReviewPass(artifacts = {}) {
215
+ const passes = Array.isArray(artifacts.reviewPasses) ? artifacts.reviewPasses : [];
216
+ return passes.at(-1) || null;
217
+ }
218
+
219
+ function latestReviewPassIsPrompted(artifacts = {}) {
220
+ return latestReviewPass(artifacts)?.status === "prompted";
221
+ }
222
+
223
+ async function readPromptFromAbsolutePath(filePath = "") {
224
+ return filePath ? readTextIfExists(filePath) : "";
225
+ }
226
+
227
+ async function readReviewPromptForStep(paths, artifacts = {}) {
228
+ const latestPass = latestReviewPass(artifacts);
229
+ if (latestPass?.status === "prompted") {
230
+ const prompt = await readPromptFromAbsolutePath(latestPass.promptPath);
231
+ if (prompt) {
232
+ return prompt;
233
+ }
234
+ }
235
+ return "";
236
+ }
237
+
201
238
  const PROMPT_ARTIFACT_BY_STEP_ID = Object.freeze({
202
239
  issue_drafted: "issue_draft.md",
203
240
  issue_details_gathered: "issue_details.md",
@@ -218,10 +255,13 @@ async function promptArtifactForStep(paths, stepId) {
218
255
  return PROMPT_ARTIFACT_BY_STEP_ID[normalizedStepId] || "";
219
256
  }
220
257
 
221
- async function readPromptForStep(paths, stepId) {
258
+ async function readPromptForStep(paths, stepId, artifacts = {}) {
222
259
  if (!stepCanExposeStoredPrompt(stepId)) {
223
260
  return "";
224
261
  }
262
+ if (REVIEW_STEP_IDS.includes(normalizeStepId(stepId))) {
263
+ return readReviewPromptForStep(paths, artifacts);
264
+ }
225
265
  const promptArtifact = await promptArtifactForStep(paths, stepId);
226
266
  if (promptArtifact) {
227
267
  const prompt = await readTextIfExists(path.join(paths.sessionRoot, "prompts", promptArtifact));
@@ -229,7 +269,7 @@ async function readPromptForStep(paths, stepId) {
229
269
  return prompt;
230
270
  }
231
271
  }
232
- return readTextIfExists(path.join(paths.sessionRoot, "prompt.md"));
272
+ return "";
233
273
  }
234
274
 
235
275
  async function readStepFileNames(stepsRoot) {
@@ -253,11 +293,6 @@ async function readCompletedSteps(paths) {
253
293
  return applyReviewPassCompletionOverlay(paths, normalizeKnownStepIds([...globalStepIds, ...cycleStepIds]));
254
294
  }
255
295
 
256
- const REVIEW_STEP_IDS = Object.freeze([
257
- "review_prompt_rendered",
258
- "review_changes_accepted"
259
- ]);
260
-
261
296
  async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
262
297
  const completed = new Set(completedSteps);
263
298
  if (!REVIEW_STEP_IDS.some((stepId) => completed.has(stepId))) {
@@ -315,7 +350,7 @@ async function readCycles(paths, activeCycle) {
315
350
  }
316
351
  }
317
352
  } catch {
318
- // Missing cycle directories are normal for older sessions.
353
+ // No cycle directory exists until a session enters a repeatable work cycle.
319
354
  }
320
355
  }
321
356
  return Promise.all([...cycles]
@@ -494,6 +529,23 @@ function cloneContractValue(value) {
494
529
  );
495
530
  }
496
531
 
532
+ async function publicCodexContract(codex = null) {
533
+ if (!codex || typeof codex !== "object" || Array.isArray(codex)) {
534
+ return null;
535
+ }
536
+ const clonedCodex = cloneContractValue(codex);
537
+ const resolvePrompt = clonedCodex.responseContract?.resolvePrompt;
538
+ const templateFile = normalizeText(resolvePrompt?.templateFile || "");
539
+ if (templateFile) {
540
+ const template = await readTextIfExists(path.join(PROMPT_DIRECTORY, templateFile));
541
+ clonedCodex.responseContract.resolvePrompt = {
542
+ ...resolvePrompt,
543
+ template
544
+ };
545
+ }
546
+ return clonedCodex;
547
+ }
548
+
497
549
  function stepRepeatabilityContract(stepId) {
498
550
  if (!CYCLE_STEP_IDS.includes(normalizeStepId(stepId))) {
499
551
  return {
@@ -513,6 +565,8 @@ function stepRepeatabilityContract(stepId) {
513
565
 
514
566
  function publicStepDefinition(step, index) {
515
567
  return {
568
+ automation: cloneContractValue(step.automation || { mode: "manual" }),
569
+ ...(step.codex ? { codex: cloneContractValue(step.codex) } : {}),
516
570
  description: step.description,
517
571
  displayGroupId: step.displayGroupId || "",
518
572
  displayGroupLabel: step.displayGroupLabel || "",
@@ -523,6 +577,7 @@ function publicStepDefinition(step, index) {
523
577
  label: step.label,
524
578
  ...stepRepeatabilityContract(step.id),
525
579
  requiresExplicitRun: step.requiresExplicitRun === true,
580
+ submitOptions: cloneContractValue(step.submitOptions || {}),
526
581
  utilityActions: cloneContractValue(step.utilityActions || [])
527
582
  };
528
583
  }
@@ -534,8 +589,7 @@ function buildStepDefinitions() {
534
589
  function stepIsRetryableWhenBlocked(stepId) {
535
590
  return [
536
591
  "automated_checks_run",
537
- "deep_ui_check_run",
538
- "doctor_run"
592
+ "deep_ui_check_run"
539
593
  ].includes(normalizeStepId(stepId));
540
594
  }
541
595
 
@@ -607,11 +661,13 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
607
661
  if (step.id === "review_changes_accepted") {
608
662
  alternateActions.push({
609
663
  id: "request_another_review_pass",
664
+ helpText: "Use this only when important review findings remain. Studio will record these notes and loop back to Codex review.",
610
665
  input: {
611
666
  formatHint: "markdown",
612
667
  label: "What important findings remain?",
613
668
  multiline: true,
614
669
  name: "reviewFindings",
670
+ placeholder: "List the specific findings Codex still needs to address.",
615
671
  required: true,
616
672
  type: "text"
617
673
  },
@@ -626,6 +682,7 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
626
682
  if (step.id === "pr_finalized") {
627
683
  alternateActions.push({
628
684
  id: "close_without_merge",
685
+ helpText: "Leave the PR open, record the reason, and remove the session worktree without merging.",
629
686
  input: {
630
687
  formatHint: "markdown",
631
688
  label: "Why is this session finishing without merge?",
@@ -679,10 +736,13 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
679
736
  alternateActions,
680
737
  buttonLabel: dynamicButtonLabel,
681
738
  description: dynamicDescription,
739
+ displayGroupId: step.displayGroupId,
740
+ displayGroupLabel: step.displayGroupLabel,
682
741
  index: STEP_IDS.indexOf(step.id),
683
742
  input: cloneContractValue(step.input),
684
743
  kind: step.kind,
685
744
  label: dynamicButtonLabel,
745
+ automation: cloneContractValue(step.automation || { mode: "manual" }),
686
746
  ...stepRepeatabilityContract(step.id),
687
747
  requiredInput: cloneContractValue(step.input),
688
748
  requiresExplicitRun: step.requiresExplicitRun === true,
@@ -690,13 +750,29 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
690
750
  retryable: artifacts.status === SESSION_STATUS.BLOCKED && stepIsRetryableWhenBlocked(step.id),
691
751
  skipReason: skipReasonForStep(step.id, artifacts),
692
752
  stepId: step.id,
753
+ submitOptions: cloneContractValue(step.submitOptions || {}),
693
754
  utilityActions: cloneContractValue(dynamicUtilityActions)
694
755
  };
695
756
  }
696
757
 
697
- function buildCodexHandoff(stepId, _artifacts = {}) {
758
+ function rawCodexHandoff(stepId, artifacts = {}) {
759
+ if (normalizeStepId(stepId) === "review_changes_accepted" && latestReviewPassIsPrompted(artifacts)) {
760
+ return cloneContractValue(REVIEW_EXECUTION_CODEX_HANDOFF);
761
+ }
698
762
  const step = STEP_DEFINITION_BY_ID[stepId];
699
- return step?.codex ? cloneContractValue(step.codex) : null;
763
+ const codex = step?.codex ? cloneContractValue(step.codex) : null;
764
+ if (
765
+ codex &&
766
+ normalizeStepId(stepId) === "plan_made" &&
767
+ normalizeCycleNumber(artifacts.activeCycle || "") !== "001"
768
+ ) {
769
+ codex.promptIntroText = "Codex will create a revised implementation plan based on the rework notes.";
770
+ }
771
+ return codex;
772
+ }
773
+
774
+ async function buildCodexHandoff(stepId, artifacts = {}) {
775
+ return publicCodexContract(rawCodexHandoff(stepId, artifacts));
700
776
  }
701
777
 
702
778
  async function readSessionArtifacts(paths) {
@@ -798,7 +874,7 @@ async function readSessionArtifacts(paths) {
798
874
  (completedSteps.includes(currentStep) || currentStepIndex < 0 || currentStepIndex > nextStepIndex)
799
875
  ? nextStep
800
876
  : currentStep || nextStep;
801
- const prompt = await readPromptForStep(paths, effectiveCurrentStep);
877
+ const prompt = await readPromptForStep(paths, effectiveCurrentStep, { reviewPasses });
802
878
 
803
879
  return {
804
880
  codexThreadId,
@@ -969,7 +1045,7 @@ async function buildSessionResponse(paths, {
969
1045
  commandLogPath: artifacts.commandLogPath || "",
970
1046
  stepDefinitions: buildStepDefinitions(),
971
1047
  currentStepAction: buildCurrentStepAction(currentStep, artifacts),
972
- codex: codex === undefined ? buildCodexHandoff(currentStep, artifacts) : cloneContractValue(codex),
1048
+ codex: codex === undefined ? await buildCodexHandoff(currentStep, artifacts) : await publicCodexContract(codex),
973
1049
  prompt: responsePrompt,
974
1050
  nextCommand: buildNextCommand(paths.sessionId || "", currentStep),
975
1051
  issueUrl: artifacts.issueUrl || "",
@@ -1120,7 +1196,8 @@ async function failSession(paths, {
1120
1196
  repairCommand = "",
1121
1197
  preconditions = [],
1122
1198
  status = SESSION_STATUS.BLOCKED,
1123
- prompt = ""
1199
+ prompt = "",
1200
+ codex = undefined
1124
1201
  }) {
1125
1202
  if (paths.sessionRoot && await fileExists(paths.sessionRoot)) {
1126
1203
  await markStatus(paths, status);
@@ -1129,6 +1206,7 @@ async function failSession(paths, {
1129
1206
  ok: false,
1130
1207
  status,
1131
1208
  prompt,
1209
+ codex,
1132
1210
  preconditions,
1133
1211
  errors: [
1134
1212
  createError({
@@ -9,6 +9,7 @@ import path from "node:path";
9
9
  import {
10
10
  BLUEPRINT_CODEX_HANDOFF,
11
11
  AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF,
12
+ CYCLE_STEP_IDS,
12
13
  DEEP_UI_CHECK_CODEX_HANDOFF,
13
14
  ISSUE_DETAILS_CODEX_HANDOFF,
14
15
  PLAN_EXECUTION_CODEX_HANDOFF,
@@ -16,6 +17,7 @@ import {
16
17
  REVIEW_EXECUTION_CODEX_HANDOFF,
17
18
  SESSION_STATUS,
18
19
  SESSION_WORKFLOW_VERSION,
20
+ STEP_DEFINITION_BY_ID,
19
21
  STEP_DEFINITIONS,
20
22
  STEP_IDS,
21
23
  STEP_PRECONDITION_NAMES
@@ -165,6 +167,15 @@ function extractMarkedText(value = "", marker = "") {
165
167
  return normalizeText(matches.length > 0 ? matches[matches.length - 1][1] : "");
166
168
  }
167
169
 
170
+ function extractMarkedField(value = "", fieldName = "") {
171
+ const normalizedFieldName = normalizeText(fieldName);
172
+ if (!normalizedFieldName) {
173
+ return "";
174
+ }
175
+ const pattern = new RegExp(`^\\s*${normalizedFieldName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\s*:\\s*(.+)$`, "imu");
176
+ return normalizeText(pattern.exec(normalizeText(value))?.[1] || "");
177
+ }
178
+
168
179
  function extractIssueTitle(value = "") {
169
180
  return extractMarkedText(value, "issue_title");
170
181
  }
@@ -211,6 +222,62 @@ async function writePromptArtifact(paths, fileName, prompt) {
211
222
  await writeTextFile(path.join(paths.sessionRoot, "prompts", fileName), prompt);
212
223
  }
213
224
 
225
+ async function codexResultPath(paths, stepId) {
226
+ if (CYCLE_STEP_IDS.includes(stepId)) {
227
+ const activeCycle = await readActiveCycle(paths);
228
+ return path.join(cycleRootPath(paths, activeCycle), "codex_results", `${stepId}.md`);
229
+ }
230
+ return path.join(paths.sessionRoot, "codex_results", `${stepId}.md`);
231
+ }
232
+
233
+ function codexResponseContractForStep(stepId) {
234
+ const contract = STEP_DEFINITION_BY_ID[stepId]?.codex?.responseContract;
235
+ return contract && typeof contract === "object" && !Array.isArray(contract) ? contract : null;
236
+ }
237
+
238
+ async function requireCodexStepResult(paths, stepId, result, preconditions = [], contractOverride = null) {
239
+ const contract = contractOverride || codexResponseContractForStep(stepId);
240
+ if (contract?.required !== true || !contract.marker) {
241
+ return null;
242
+ }
243
+
244
+ const source = String(result || "");
245
+ if (!source.trim()) {
246
+ return failSession(paths, {
247
+ code: "codex_result_required",
248
+ message: `The ${STEP_DEFINITION_BY_ID[stepId]?.label || stepId} step has already produced a Codex prompt. Paste Codex's final marked result with --codex-result - before JSKIT records the receipt.`,
249
+ repairCommand: `jskit session ${paths.sessionId} step --codex-result -`,
250
+ preconditions
251
+ });
252
+ }
253
+
254
+ const markedResult = extractMarkedText(source, contract.marker);
255
+ if (!markedResult) {
256
+ return failSession(paths, {
257
+ code: "codex_result_marker_missing",
258
+ message: `Codex output for ${STEP_DEFINITION_BY_ID[stepId]?.label || stepId} must include [${contract.marker}]... [/${contract.marker}] before JSKIT can advance.`,
259
+ repairCommand: `jskit session ${paths.sessionId} step --codex-result -`,
260
+ preconditions
261
+ });
262
+ }
263
+ const stepField = normalizeText(contract.stepField);
264
+ if (stepField) {
265
+ const resultStep = extractMarkedField(markedResult, stepField);
266
+ if (resultStep !== stepId) {
267
+ return failSession(paths, {
268
+ code: "codex_result_step_mismatch",
269
+ message: `Codex output for ${STEP_DEFINITION_BY_ID[stepId]?.label || stepId} must include ${stepField}: ${stepId} inside [${contract.marker}] before JSKIT can advance.`,
270
+ repairCommand: `jskit session ${paths.sessionId} step --codex-result -`,
271
+ preconditions
272
+ });
273
+ }
274
+ }
275
+
276
+ await writeTextFile(await codexResultPath(paths, stepId), source);
277
+ await appendAgentDecisions(paths, extractAgentDecisions(source));
278
+ return null;
279
+ }
280
+
214
281
  function commandText(command, args = []) {
215
282
  return [command, ...args].map((part) => {
216
283
  const value = String(part || "");
@@ -1050,11 +1117,15 @@ async function makePlan(paths, options = {}, context = {}) {
1050
1117
  });
1051
1118
  }
1052
1119
 
1053
- async function renderPlanExecutionPrompt(paths, _options = {}, context = {}) {
1120
+ async function renderPlanExecutionPrompt(paths, options = {}, context = {}) {
1054
1121
  const preconditions = context.preconditions || [];
1055
1122
  const activeCycle = await readActiveCycle(paths);
1056
1123
  const executionPromptPath = path.join(paths.sessionRoot, "prompts", cyclePlanExecutionPromptFileName(activeCycle));
1057
1124
  if (await fileExists(executionPromptPath)) {
1125
+ const codexResultFailure = await requireCodexStepResult(paths, "plan_executed", options.codexResult, preconditions);
1126
+ if (codexResultFailure) {
1127
+ return codexResultFailure;
1128
+ }
1058
1129
  await writeReceipt(paths, "plan_executed", `Cycle ${activeCycle} plan execution completed by Codex.`);
1059
1130
  await markStatus(paths, SESSION_STATUS.RUNNING);
1060
1131
  return buildSessionResponse(paths, {
@@ -1402,7 +1473,8 @@ async function acceptReviewChanges(paths, options = {}) {
1402
1473
  async function runAutomatedChecks(paths, {
1403
1474
  stepId,
1404
1475
  label
1405
- }) {
1476
+ }, options = {}, context = {}) {
1477
+ const preconditions = context.preconditions || [];
1406
1478
  const [command, args] = await doctorCommandForWorktree(paths.worktree);
1407
1479
  const promptFileName = `${stepId}.md`;
1408
1480
  const promptPath = path.join(paths.sessionRoot, "prompts", promptFileName);
@@ -1411,6 +1483,10 @@ async function runAutomatedChecks(paths, {
1411
1483
  const checkCommand = [command, ...args].join(" ");
1412
1484
 
1413
1485
  if (await fileExists(promptPath)) {
1486
+ const codexResultFailure = await requireCodexStepResult(paths, stepId, options.codexResult, preconditions);
1487
+ if (codexResultFailure) {
1488
+ return codexResultFailure;
1489
+ }
1414
1490
  await writeTextFile(
1415
1491
  path.join(checksRoot, `${stepId}.json`),
1416
1492
  `${JSON.stringify({
@@ -1423,7 +1499,9 @@ async function runAutomatedChecks(paths, {
1423
1499
  );
1424
1500
  await writeReceipt(paths, stepId, `${label} completed by Codex: ${checkCommand}.`);
1425
1501
  await markStatus(paths, SESSION_STATUS.RUNNING);
1426
- return buildSessionResponse(paths);
1502
+ return buildSessionResponse(paths, {
1503
+ preconditions
1504
+ });
1427
1505
  }
1428
1506
 
1429
1507
  const prompt = await renderPrompt(paths, "automated_checks.md", {
@@ -1443,6 +1521,7 @@ async function runAutomatedChecks(paths, {
1443
1521
  await markStatus(paths, SESSION_STATUS.WAITING_FOR_USER);
1444
1522
  return buildSessionResponse(paths, {
1445
1523
  codex: AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF,
1524
+ preconditions,
1446
1525
  prompt,
1447
1526
  status: SESSION_STATUS.WAITING_FOR_USER
1448
1527
  });
@@ -1501,6 +1580,10 @@ async function runDeepUiCheck(paths, {
1501
1580
  const promptFileName = `${stepId}.md`;
1502
1581
  const promptPath = path.join(paths.sessionRoot, "prompts", promptFileName);
1503
1582
  if (await fileExists(promptPath)) {
1583
+ const codexResultFailure = await requireCodexStepResult(paths, stepId, options.codexResult, preconditions);
1584
+ if (codexResultFailure) {
1585
+ return codexResultFailure;
1586
+ }
1504
1587
  await writeReceipt(paths, stepId, `${label} completed by Codex.`);
1505
1588
  await markStatus(paths, SESSION_STATUS.RUNNING);
1506
1589
  return buildSessionResponse(paths, {
@@ -1644,7 +1727,7 @@ async function commitAcceptedChanges(paths, _options = {}, context = {}) {
1644
1727
  });
1645
1728
  }
1646
1729
 
1647
- async function updateBlueprint(paths, _options = {}, context = {}) {
1730
+ async function updateBlueprint(paths, options = {}, context = {}) {
1648
1731
  const preconditions = context.preconditions || [];
1649
1732
  const issueText = await readTrimmedFile(path.join(paths.sessionRoot, "issue.md"));
1650
1733
  const issueTitle = await readTrimmedFile(path.join(paths.sessionRoot, "issue_title")) || titleFromIssue(issueText);
@@ -1657,6 +1740,10 @@ async function updateBlueprint(paths, _options = {}, context = {}) {
1657
1740
  const blueprintPromptPath = path.join(paths.sessionRoot, "prompts", "update_blueprint.md");
1658
1741
 
1659
1742
  if (await fileExists(blueprintPromptPath)) {
1743
+ const codexResultFailure = await requireCodexStepResult(paths, "blueprint_updated", options.codexResult, preconditions);
1744
+ if (codexResultFailure) {
1745
+ return codexResultFailure;
1746
+ }
1660
1747
  const changedFiles = await changedFilesInWorktree(paths);
1661
1748
  const unexpectedChanges = changedFiles.filter((file) => file !== ".jskit/APP_BLUEPRINT.md");
1662
1749
  if (unexpectedChanges.length > 0) {
@@ -1755,68 +1842,6 @@ async function doctorCommandForWorktree(worktree) {
1755
1842
  return ["npx", ["--no-install", "jskit", "app", "verify"]];
1756
1843
  }
1757
1844
 
1758
- async function runDoctor(paths) {
1759
- const repairCommitFailure = await maybeCommitDoctorRepair(paths);
1760
- if (repairCommitFailure) {
1761
- return repairCommitFailure;
1762
- }
1763
- const [command, args] = await doctorCommandForWorktree(paths.worktree);
1764
- const result = await runLoggedCommand(paths, "doctor_run", command, args, {
1765
- cwd: paths.worktree,
1766
- timeout: 1000 * 60 * 15
1767
- });
1768
- await writeTextFile(path.join(paths.sessionRoot, "doctor.log"), result.output);
1769
- if (!result.ok) {
1770
- const prompt = await renderPrompt(paths, "doctor_failure.md", {
1771
- doctor_output: result.output
1772
- });
1773
- await writePromptArtifact(paths, "doctor_failure.md", prompt);
1774
- return failSession(paths, {
1775
- code: "doctor_failed",
1776
- message: "Doctor/verification command failed. Paste the failure prompt into Codex, then rerun this step.",
1777
- repairCommand: `${command} ${args.join(" ")}`,
1778
- prompt
1779
- });
1780
- }
1781
- await writeReceipt(paths, "doctor_run", `Doctor command passed: ${command} ${args.join(" ")}.`);
1782
- await markStatus(paths, SESSION_STATUS.RUNNING);
1783
- return buildSessionResponse(paths);
1784
- }
1785
-
1786
- async function maybeCommitDoctorRepair(paths) {
1787
- if (!await fileExists(path.join(paths.sessionRoot, "doctor.log")) || await fileExists(path.join(paths.sessionRoot, "steps", "doctor_run"))) {
1788
- return null;
1789
- }
1790
- const status = await worktreeStatus(paths.worktree);
1791
- if (!status.ok) {
1792
- return failSession(paths, {
1793
- code: "git_status_failed",
1794
- message: status.output || "Failed to inspect verification repair changes.",
1795
- repairCommand: `git -C ${paths.worktree} status --short`
1796
- });
1797
- }
1798
- if (status.changedFiles.length < 1) {
1799
- return null;
1800
- }
1801
- const result = await commitWorktree(paths, {
1802
- message: `Verification repairs for ${paths.sessionId}`
1803
- });
1804
- if (!result.ok) {
1805
- return failSession(paths, {
1806
- code: "doctor_repair_commit_failed",
1807
- message: result.output || "Failed to commit verification repair changes.",
1808
- repairCommand: `git -C ${paths.worktree} status --short`
1809
- });
1810
- }
1811
- await writeTextFile(path.join(paths.sessionRoot, "doctor_repair_commit.json"), `${JSON.stringify({
1812
- changedFiles: result.changedFiles || [],
1813
- commit: await currentHead(paths),
1814
- committedAt: timestampForReceipt(),
1815
- ok: true
1816
- }, null, 2)}\n`);
1817
- return null;
1818
- }
1819
-
1820
1845
  async function commitLinesSinceBase(paths) {
1821
1846
  const baseCommit = await readTrimmedFile(path.join(paths.sessionRoot, "base_commit"));
1822
1847
  const args = baseCommit
@@ -2536,10 +2561,10 @@ const STEP_RUNNERS = Object.freeze({
2536
2561
  issue_details_gathered: saveIssueDetails,
2537
2562
  plan_made: makePlan,
2538
2563
  plan_executed: renderPlanExecutionPrompt,
2539
- automated_checks_run: (paths) => runAutomatedChecks(paths, {
2564
+ automated_checks_run: (paths, options, context) => runAutomatedChecks(paths, {
2540
2565
  label: "Automated checks",
2541
2566
  stepId: "automated_checks_run"
2542
- }),
2567
+ }, options, context),
2543
2568
  deep_ui_check_run: (paths, options, context) => runDeepUiCheck(paths, {
2544
2569
  label: "Deep UI check",
2545
2570
  phase: "pre_review",
@@ -2550,7 +2575,6 @@ const STEP_RUNNERS = Object.freeze({
2550
2575
  user_check_completed: userCheck,
2551
2576
  changes_committed: commitAcceptedChanges,
2552
2577
  blueprint_updated: updateBlueprint,
2553
- doctor_run: runDoctor,
2554
2578
  final_report_created: createFinalReport,
2555
2579
  pr_created: createPr,
2556
2580
  pr_finalized: finalizePr,
@@ -1,35 +0,0 @@
1
- The JSKIT session doctor or verification step failed.
2
-
3
- Session:
4
-
5
- {{session_id}}
6
-
7
- Worktree:
8
-
9
- {{worktree}}
10
-
11
- Failure output:
12
-
13
- {{doctor_output}}
14
-
15
- Fix the root cause in this worktree. Do not silence the failure or remove checks to make the step pass.
16
-
17
- Diagnosis rules:
18
-
19
- - Re-read the issue, `issue_details.md`, the active cycle plan file, `agent_decisions.md`, `.jskit/APP_BLUEPRINT.md`, `.jskit/helper-map.md`, check receipts, and UI check receipts before repairing.
20
- - Identify whether the failure is dependency/setup, JSKIT metadata, generated contract drift, routing/surface wiring, CRUD ownership, UI verification receipt, test-auth, or ordinary application code.
21
- - Prefer repairing the JSKIT-owned contract or generated metadata over adding local-path hacks.
22
- - Do not run `npm install` only because optional agent docs are missing. Run installs only when the failure or a JSKIT setup/session step requires dependency repair.
23
- - If a generator/package command is the correct repair, use the `npx --no-install jskit` command rather than hand-recreating generated structure.
24
- - For UI receipt failures, run the relevant Playwright check and record it with `npx --no-install jskit app verify-ui --command "<playwright command>" --feature "<label>" --auth-mode <mode>` when possible.
25
- - If login is required, use the app's development auth bootstrap path rather than a live external auth flow.
26
-
27
- Do not push, open a PR, merge, or remove the worktree. When the fix is ready, report the root cause, files changed, verification, and anything still unverified. The user or Studio will rerun the JSKIT session step.
28
-
29
- If the repair records a meaningful verification decision, tradeoff, missing coverage, or root-cause explanation future steps should know, include concise entries with reasons in this exact marker block:
30
-
31
- ```text
32
- [agent_decisions]
33
- <verification or repair decisions, or "No new decisions.">
34
- [/agent_decisions]
35
- ```