@jskit-ai/jskit-cli 0.2.89 → 0.2.90

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 (25) hide show
  1. package/package.json +4 -4
  2. package/src/server/commandHandlers/session.js +173 -142
  3. package/src/server/core/argParser.js +0 -4
  4. package/src/server/core/commandCatalog.js +47 -35
  5. package/src/server/sessionRuntime/constants.js +125 -348
  6. package/src/server/sessionRuntime/io.js +2 -2
  7. package/src/server/sessionRuntime/preconditions.js +39 -143
  8. package/src/server/sessionRuntime/promptRenderer.js +2 -15
  9. package/src/server/sessionRuntime/prompts/app_blueprint.md +2 -7
  10. package/src/server/sessionRuntime/prompts/{automated_checks.md → automated_checks_run.md} +2 -17
  11. package/src/server/sessionRuntime/prompts/{update_blueprint.md → blueprint_updated.md} +2 -11
  12. package/src/server/sessionRuntime/prompts/{deep_ui_check.md → deep_ui_check_run.md} +2 -19
  13. package/src/server/sessionRuntime/prompts/final_report_created.md +44 -0
  14. package/src/server/sessionRuntime/prompts/issue_created.md +26 -0
  15. package/src/server/sessionRuntime/prompts/issue_prompt_rendered.md +1 -0
  16. package/src/server/sessionRuntime/prompts/{plan_issue.md → make_plan.md} +8 -37
  17. package/src/server/sessionRuntime/prompts/{execute_plan.md → plan_executed.md} +4 -29
  18. package/src/server/sessionRuntime/prompts/{prepare_pr_merge.md → pr_merge_prepared.md} +3 -3
  19. package/src/server/sessionRuntime/prompts/{resolve_deslop_findings.md → review_changes_accepted_resolve.md} +2 -6
  20. package/src/server/sessionRuntime/prompts/{review_changes.md → review_prompt_rendered.md} +3 -28
  21. package/src/server/sessionRuntime/prompts/{user_check.md → user_check_completed.md} +1 -11
  22. package/src/server/sessionRuntime/responses.js +431 -292
  23. package/src/server/sessionRuntime.js +1201 -926
  24. package/src/server/sessionRuntime/prompts/issue_details.md +0 -49
  25. package/src/server/sessionRuntime/prompts/new_issue.md +0 -46
@@ -2,8 +2,10 @@ import { mkdir, readdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import {
4
4
  CYCLE_STEP_IDS,
5
+ DEPENDENCIES_INSTALL_RESULT_FILE,
6
+ ISSUE_FILE_CODEX_HANDOFF,
7
+ ISSUE_DEFINITION_CODEX_HANDOFF,
5
8
  JSKIT_CLI_SHELL_COMMAND,
6
- PROMPT_DIRECTORY,
7
9
  REVIEW_EXECUTION_CODEX_HANDOFF,
8
10
  REVIEW_PASS_LIMIT,
9
11
  SESSION_WORKFLOW_VERSION,
@@ -19,7 +21,7 @@ import {
19
21
  readTextIfExists,
20
22
  readTrimmedFile,
21
23
  runGitInWorktree,
22
- timestampForReceipt,
24
+ timestampForStepRecord,
23
25
  writeTextFile
24
26
  } from "./io.js";
25
27
  import {
@@ -69,6 +71,11 @@ const ACCEPTED_CHANGES_NOOP_WARNING = createWarning({
69
71
  message: "No accepted worktree changes were found; continuing without a new commit."
70
72
  });
71
73
 
74
+ function issueNumberFromUrl(issueUrl = "") {
75
+ const match = /\/issues\/(\d+)(?:\b|$)/u.exec(String(issueUrl || ""));
76
+ return match ? match[1] : "";
77
+ }
78
+
72
79
  function normalizeStepId(stepId) {
73
80
  return normalizeText(stepId);
74
81
  }
@@ -126,10 +133,6 @@ async function readActiveCycle(paths) {
126
133
  return normalizeCycleNumber(cycle || DEFAULT_ACTIVE_CYCLE);
127
134
  }
128
135
 
129
- async function writeActiveCycle(paths, cycle) {
130
- await writeTextFile(path.join(paths.sessionRoot, "active_cycle"), `${normalizeCycleNumber(cycle)}\n`);
131
- }
132
-
133
136
  function cycleStepsRoot(paths, cycle) {
134
137
  return path.join(paths.sessionRoot, "steps", cycleDirectoryName(cycle));
135
138
  }
@@ -138,18 +141,6 @@ function cycleRoot(paths, cycle) {
138
141
  return path.join(paths.sessionRoot, "cycles", cycleDirectoryName(cycle));
139
142
  }
140
143
 
141
- function cyclePlanPath(paths, cycle) {
142
- return path.join(cycleRoot(paths, cycle), "plan.md");
143
- }
144
-
145
- function cyclePlanPromptFileName(cycle) {
146
- return `cycle_${normalizeCycleNumber(cycle)}_plan_request.md`;
147
- }
148
-
149
- function cyclePlanExecutionPromptFileName(cycle) {
150
- return `cycle_${normalizeCycleNumber(cycle)}_plan_execution.md`;
151
- }
152
-
153
144
  function normalizeReviewPassNumber(value = "") {
154
145
  const normalized = normalizeText(value).replace(/^pass_/u, "");
155
146
  if (!/^\d+$/u.test(normalized)) {
@@ -204,7 +195,7 @@ async function readReviewPassInfo(paths, pass) {
204
195
  pass: normalizedPass,
205
196
  label: reviewPassDirectoryName(normalizedPass),
206
197
  status,
207
- promptPath: prompt?.promptPath || path.join(root, "prompt.md"),
198
+ promptPath: prompt?.promptPath || path.join(root, "review_prompt_rendered"),
208
199
  acceptedAt: accepted?.acceptedAt || "",
209
200
  changedFiles,
210
201
  commit: "",
@@ -248,27 +239,6 @@ async function readReviewPromptForStep(paths, artifacts = {}) {
248
239
  return "";
249
240
  }
250
241
 
251
- const PROMPT_ARTIFACT_BY_STEP_ID = Object.freeze({
252
- issue_drafted: "issue_draft.md",
253
- issue_details_gathered: "issue_details.md",
254
- deep_ui_check_run: "deep_ui_check_run.md",
255
- automated_checks_run: "automated_checks_run.md",
256
- blueprint_updated: "update_blueprint.md",
257
- pr_merge_prepared: "prepare_pr_merge.md",
258
- user_check_completed: "user_check.md"
259
- });
260
-
261
- async function promptArtifactForStep(paths, stepId) {
262
- const normalizedStepId = normalizeStepId(stepId);
263
- if (normalizedStepId === "plan_made") {
264
- return cyclePlanPromptFileName(await readActiveCycle(paths));
265
- }
266
- if (normalizedStepId === "plan_executed") {
267
- return cyclePlanExecutionPromptFileName(await readActiveCycle(paths));
268
- }
269
- return PROMPT_ARTIFACT_BY_STEP_ID[normalizedStepId] || "";
270
- }
271
-
272
242
  async function readPromptForStep(paths, stepId, artifacts = {}) {
273
243
  if (!stepCanExposeStoredPrompt(stepId)) {
274
244
  return "";
@@ -276,13 +246,6 @@ async function readPromptForStep(paths, stepId, artifacts = {}) {
276
246
  if (REVIEW_STEP_IDS.includes(normalizeStepId(stepId))) {
277
247
  return readReviewPromptForStep(paths, artifacts);
278
248
  }
279
- const promptArtifact = await promptArtifactForStep(paths, stepId);
280
- if (promptArtifact) {
281
- const prompt = await readTextIfExists(path.join(paths.sessionRoot, "prompts", promptArtifact));
282
- if (prompt) {
283
- return prompt;
284
- }
285
- }
286
249
  return "";
287
250
  }
288
251
 
@@ -299,11 +262,18 @@ async function readStepFileNames(stepsRoot) {
299
262
 
300
263
  async function readCompletedSteps(paths) {
301
264
  const stepsRoot = path.join(paths.sessionRoot, "steps");
302
- const activeCycle = await readActiveCycle(paths);
303
265
  const globalStepIds = normalizeKnownStepIds(
304
266
  (await readStepFileNames(stepsRoot)).filter((stepId) => !isCycleStepId(stepId))
305
267
  );
306
- const cycleStepIds = normalizeKnownStepIds(await readStepFileNames(cycleStepsRoot(paths, activeCycle)));
268
+ const cycleStepIds = [];
269
+ try {
270
+ const entries = await readdir(stepsRoot, { withFileTypes: true });
271
+ for (const entry of entries.filter((item) => item.isDirectory() && /^cycle_\d+$/u.test(item.name)).sort((left, right) => left.name.localeCompare(right.name))) {
272
+ cycleStepIds.push(...await readStepFileNames(path.join(stepsRoot, entry.name)));
273
+ }
274
+ } catch {
275
+ // Legacy sessions may not have cycle record directories.
276
+ }
307
277
  return applyReviewPassCompletionOverlay(paths, normalizeKnownStepIds([...globalStepIds, ...cycleStepIds]));
308
278
  }
309
279
 
@@ -315,7 +285,6 @@ async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
315
285
  const reviewPasses = await readReviewPasses(paths);
316
286
  const latestPass = reviewPasses.at(-1);
317
287
  if (!latestPass) {
318
- REVIEW_STEP_IDS.forEach((stepId) => completed.delete(stepId));
319
288
  return normalizeKnownStepIds([...completed]);
320
289
  }
321
290
  const latestPassAccepted = latestPass.status === "accepted" || latestPass.status === "no_changes";
@@ -324,11 +293,6 @@ async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
324
293
  REVIEW_STEP_IDS.forEach((stepId) => completed.delete(stepId));
325
294
  return normalizeKnownStepIds([...completed]);
326
295
  }
327
- if (latestPass.status === "prompted") {
328
- completed.add("review_prompt_rendered");
329
- completed.delete("review_changes_accepted");
330
- return normalizeKnownStepIds([...completed]);
331
- }
332
296
  if (latestPass.status === "accepted" || latestPass.status === "no_changes") {
333
297
  REVIEW_STEP_IDS.forEach((stepId) => completed.add(stepId));
334
298
  }
@@ -340,7 +304,7 @@ async function readCycleInfo(paths, cycle) {
340
304
  const root = cycleRoot(paths, normalizedCycle);
341
305
  const userCheckPassed = await readTextIfExists(path.join(cycleStepsRoot(paths, normalizedCycle), "user_check_completed"));
342
306
  const userCheckFailed = await readTextIfExists(path.join(cycleStepsRoot(paths, normalizedCycle), "user_check_failed"));
343
- const reworkRequestPath = path.join(root, "rework_request.md");
307
+ const reworkRequestPath = path.join(root, "rework_request");
344
308
  const reworkRequest = await readTextIfExists(reworkRequestPath);
345
309
  return {
346
310
  cycle: normalizedCycle,
@@ -349,7 +313,7 @@ async function readCycleInfo(paths, cycle) {
349
313
  reworkRequestPath: reworkRequest ? reworkRequestPath : "",
350
314
  status: userCheckPassed ? "passed" : userCheckFailed ? "failed" : "active",
351
315
  userCheckResult: userCheckPassed ? "passed" : userCheckFailed ? "failed" : "",
352
- userCheckReceipt: (userCheckPassed || userCheckFailed).trim()
316
+ userCheckRecord: (userCheckPassed || userCheckFailed).trim()
353
317
  };
354
318
  }
355
319
 
@@ -451,7 +415,7 @@ async function readWorktreeStatus(paths, worktreeReady) {
451
415
  };
452
416
  }
453
417
 
454
- async function readReceiptSteps(paths) {
418
+ async function readStepRecords(paths) {
455
419
  const stepsRoot = path.join(paths.sessionRoot, "steps");
456
420
  try {
457
421
  const entries = await readdir(stepsRoot, { withFileTypes: true });
@@ -460,19 +424,19 @@ async function readReceiptSteps(paths) {
460
424
  entries
461
425
  .filter((entry) => entry.isFile())
462
426
  .map((entry) => entry.name)
463
- .forEach((receiptName) => {
464
- const stepId = normalizeStepId(receiptName);
427
+ .forEach((recordName) => {
428
+ const stepId = normalizeStepId(recordName);
465
429
  if (STEP_IDS.includes(stepId)) {
466
- if (!knownStepRows.has(stepId) || receiptName === stepId) {
430
+ if (!knownStepRows.has(stepId) || recordName === stepId) {
467
431
  knownStepRows.set(stepId, {
468
- receiptName,
432
+ recordName,
469
433
  stepId
470
434
  });
471
435
  }
472
436
  return;
473
437
  }
474
438
  unknownStepRows.push({
475
- receiptName,
439
+ recordName,
476
440
  stepId
477
441
  });
478
442
  });
@@ -493,14 +457,14 @@ async function readReceiptSteps(paths) {
493
457
  return left.stepId.localeCompare(right.stepId);
494
458
  });
495
459
 
496
- const globalReceipts = await Promise.all(stepRows.map(async ({ receiptName, stepId }) => ({
460
+ const globalRecords = await Promise.all(stepRows.map(async ({ recordName, stepId }) => ({
497
461
  cycle: "",
462
+ details: (await readTextIfExists(path.join(stepsRoot, recordName))).trim(),
498
463
  label: STEP_LABEL_BY_ID[stepId] || stepId,
499
- receipt: (await readTextIfExists(path.join(stepsRoot, receiptName))).trim(),
500
464
  stepId
501
465
  })));
502
466
 
503
- const cycleReceipts = [];
467
+ const cycleRecords = [];
504
468
  const cycleDirectories = entries
505
469
  .filter((entry) => entry.isDirectory() && /^cycle_\d+$/u.test(entry.name))
506
470
  .map((entry) => entry.name)
@@ -509,18 +473,18 @@ async function readReceiptSteps(paths) {
509
473
  const cycle = normalizeCycleNumber(cycleDirectory);
510
474
  const cycleRootPath = path.join(stepsRoot, cycleDirectory);
511
475
  const cycleStepIds = await readStepFileNames(cycleRootPath);
512
- for (const receiptName of cycleStepIds) {
513
- const stepId = normalizeStepId(receiptName);
514
- cycleReceipts.push({
476
+ for (const recordName of cycleStepIds) {
477
+ const stepId = normalizeStepId(recordName);
478
+ cycleRecords.push({
515
479
  cycle,
480
+ details: (await readTextIfExists(path.join(cycleRootPath, recordName))).trim(),
516
481
  label: STEP_LABEL_BY_ID[stepId] || stepId,
517
- receipt: (await readTextIfExists(path.join(cycleRootPath, receiptName))).trim(),
518
482
  stepId
519
483
  });
520
484
  }
521
485
  }
522
486
 
523
- return [...globalReceipts, ...cycleReceipts];
487
+ return [...globalRecords, ...cycleRecords];
524
488
  } catch {
525
489
  return [];
526
490
  }
@@ -584,33 +548,15 @@ async function publicCodexContract(codex = null) {
584
548
  if (!codex || typeof codex !== "object" || Array.isArray(codex)) {
585
549
  return null;
586
550
  }
587
- const clonedCodex = cloneContractValue(codex);
588
- const resolvePrompt = clonedCodex.responseContract?.resolvePrompt;
589
- const templateFile = normalizeText(resolvePrompt?.templateFile || "");
590
- if (templateFile) {
591
- const template = await readTextIfExists(path.join(PROMPT_DIRECTORY, templateFile));
592
- clonedCodex.responseContract.resolvePrompt = {
593
- ...resolvePrompt,
594
- template
595
- };
596
- }
597
- return clonedCodex;
551
+ return cloneContractValue(codex);
598
552
  }
599
553
 
600
554
  function stepRepeatabilityContract(stepId) {
601
- if (!CYCLE_STEP_IDS.includes(normalizeStepId(stepId))) {
602
- return {
603
- repeatable: false,
604
- repeatableGroupId: "",
605
- repeatableGroupLabel: "",
606
- repeatableLabel: ""
607
- };
608
- }
609
555
  return {
610
- repeatable: true,
611
- repeatableGroupId: "rework_cycle",
612
- repeatableGroupLabel: "Rework cycle",
613
- repeatableLabel: "Cycle step"
556
+ repeatable: false,
557
+ repeatableGroupId: "",
558
+ repeatableGroupLabel: "",
559
+ repeatableLabel: ""
614
560
  };
615
561
  }
616
562
 
@@ -645,17 +591,6 @@ function stepIsRetryableWhenBlocked(stepId) {
645
591
  ].includes(normalizeStepId(stepId));
646
592
  }
647
593
 
648
- function stepIsConditional(stepId) {
649
- return [
650
- "deep_ui_check_run"
651
- ].includes(normalizeStepId(stepId));
652
- }
653
-
654
- function activeCycleInfoFromArtifacts(artifacts = {}) {
655
- const activeCycle = normalizeCycleNumber(artifacts.activeCycle || "");
656
- return (artifacts.cycles || []).find((cycle) => cycle?.cycle === activeCycle) || null;
657
- }
658
-
659
594
  function uiCheckPromptedForStep(artifacts = {}, stepId = "") {
660
595
  const normalizedStepId = normalizeStepId(stepId);
661
596
  return (artifacts.uiChecks || []).some((entry) => {
@@ -665,10 +600,8 @@ function uiCheckPromptedForStep(artifacts = {}, stepId = "") {
665
600
  }
666
601
 
667
602
  function skipReasonForStep(stepId, artifacts = {}) {
668
- const normalizedStepId = normalizeStepId(stepId);
669
- if (normalizedStepId === "deep_ui_check_run" && artifacts.uiImpact === "none") {
670
- return "uiImpact is none.";
671
- }
603
+ void stepId;
604
+ void artifacts;
672
605
  return "";
673
606
  }
674
607
 
@@ -677,53 +610,27 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
677
610
  if (!step) {
678
611
  return null;
679
612
  }
680
- const activeCycleInfo = activeCycleInfoFromArtifacts(artifacts);
681
613
  const planExecutionPrompted = artifacts.planExecution?.prompted === true;
682
614
  const planExecutionSubmitted = artifacts.planExecution?.submitted === true;
683
- const planReworkMode = step.id === "plan_made" && normalizeCycleNumber(artifacts.activeCycle || "") !== "001";
615
+ const issueDefinitionPrompted = step.id === "issue_prompt_rendered" && artifacts.issueDefinitionRequested === true;
616
+ const issueFilePromptAction = step.id === "issue_created";
617
+ const issueSubmissionAction = step.id === "issue_submitted";
618
+ const pullRequestFileAction = step.id === "final_report_created";
619
+ const prSubmissionAction = step.id === "pr_created";
684
620
  const deepUiCheckPrompted = step.id === "deep_ui_check_run" && uiCheckPromptedForStep(artifacts, "deep_ui_check_run");
685
- const hasActiveReworkRequest = Boolean(activeCycleInfo?.reworkRequestPath);
686
- const promptPhaseButtonLabel = step.kind === "codex_output" &&
687
- step.codex?.mode === "inject_prompt" &&
688
- !artifacts.prompt
689
- ? step.codex.promptActionLabel || ""
690
- : "";
691
- const buttonLabel = promptPhaseButtonLabel || step.buttonLabel;
621
+ const automatedChecksPrompted = step.id === "automated_checks_run" && (artifacts.checks || []).some((entry) => {
622
+ return normalizeStepId(entry?.stepId || "") === "automated_checks_run" &&
623
+ normalizeText(entry?.status || "") === "prompted";
624
+ });
692
625
  const alternateActions = [];
693
- if (step.id === "user_check_completed") {
694
- alternateActions.push({
695
- id: "return_to_plan_made",
696
- input: {
697
- formatHint: "markdown",
698
- label: "What needs to be reworked?",
699
- multiline: true,
700
- name: "reworkNotes",
701
- required: true,
702
- type: "text"
703
- },
704
- label: "Return to Plan made",
705
- presentation: "exclusive",
706
- requiredErrorCode: "user_check_failed",
707
- submitOptions: {
708
- userCheck: "failed"
709
- },
710
- targetStep: "plan_made"
711
- });
712
- }
713
626
  if (step.id === "review_changes_accepted") {
714
627
  alternateActions.push({
715
628
  id: "request_another_review_pass",
716
- helpText: "Use this only when important review findings remain. Studio will record these notes and loop back to Codex review.",
629
+ helpText: "Run another explicit deslop prompt before continuing.",
717
630
  input: {
718
- formatHint: "markdown",
719
- label: "What important findings remain?",
720
- multiline: true,
721
- name: "reviewFindings",
722
- placeholder: "List the specific findings Codex still needs to address.",
723
- required: true,
724
- type: "text"
631
+ type: "none"
725
632
  },
726
- label: "Run another review pass",
633
+ label: "Run deslop",
727
634
  presentation: "secondary",
728
635
  submitOptions: {
729
636
  reviewFindingsRemaining: true
@@ -731,73 +638,75 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
731
638
  targetStep: "review_prompt_rendered"
732
639
  });
733
640
  }
734
- if (step.id === "pr_finalized") {
735
- alternateActions.push({
736
- id: "skip_merge",
737
- helpText: "Leave the PR open and record the skipped-merge outcome; final cleanup removes the session worktree.",
738
- input: {
739
- type: "none"
740
- },
741
- label: "Skip merge",
742
- presentation: "secondary",
743
- submitOptions: {
744
- closeWithoutMerge: true
745
- },
746
- targetStep: "pr_finalized"
747
- });
748
- }
749
- if (step.id === "main_checkout_synced") {
750
- alternateActions.push({
751
- id: "skip_main_checkout_sync",
752
- helpText: "Leave the main checkout untouched and continue with session cleanup.",
753
- input: {
754
- type: "none"
755
- },
756
- label: "Skip sync",
757
- presentation: "secondary",
758
- submitOptions: {
759
- skipMainSync: true
760
- },
761
- targetStep: "main_checkout_synced"
762
- });
763
- }
764
641
  const dynamicButtonLabel = (() => {
765
- if (step.id === "plan_executed" && planExecutionPrompted && !planExecutionSubmitted) {
766
- return "Go to next step";
642
+ if (step.id === "issue_created" && !artifacts.issueText) {
643
+ return "Create issue file";
767
644
  }
768
- if (step.id === "deep_ui_check_run" && deepUiCheckPrompted) {
769
- return "Go to next step";
645
+ if (step.id === "issue_submitted") {
646
+ return "Create issue on GH";
770
647
  }
771
- if (step.id === "automated_checks_run" && artifacts.prompt) {
772
- return "Go to next step";
648
+ if (step.id === "pr_created") {
649
+ return "Create PR on GH";
773
650
  }
774
- if (step.id === "plan_made" && planReworkMode && !artifacts.prompt && hasActiveReworkRequest) {
775
- return "Get Codex to create revised plan";
651
+ if (step.id === "pr_merge_prepared") {
652
+ return "Merge";
776
653
  }
777
654
  if (step.id === "main_checkout_synced" && artifacts.prOutcome?.outcome && artifacts.prOutcome.outcome !== "merged") {
778
- return "Record no sync needed";
655
+ return "Sync main checkout";
779
656
  }
780
- return buttonLabel;
657
+ return step.buttonLabel;
781
658
  })();
782
659
  const dynamicDescription = (() => {
660
+ if (issueDefinitionPrompted) {
661
+ return "Codex has the issue-definition prompt. Continue after the issue is scoped clearly enough.";
662
+ }
663
+ if (issueFilePromptAction && artifacts.issueFileRequested) {
664
+ return "Codex has the issue-file prompt. Review issue.md and issue_title, then continue when ready.";
665
+ }
666
+ if (issueSubmissionAction && artifacts.issueUrl) {
667
+ return "The GitHub issue has been created. Continue when ready.";
668
+ }
669
+ if (pullRequestFileAction && artifacts.pullRequestFileRequested) {
670
+ return "Codex has the PR-file prompt. Review pull_request.md, then continue when ready.";
671
+ }
672
+ if (prSubmissionAction && artifacts.prUrl) {
673
+ return "The GitHub pull request has been created. Continue when ready.";
674
+ }
675
+ if (step.id === "pr_merge_prepared" && artifacts.prOutcome?.outcome === "merged") {
676
+ return "The pull request was merged. Use Next when ready.";
677
+ }
783
678
  if (step.id === "plan_executed" && planExecutionPrompted && !planExecutionSubmitted) {
784
- return "Codex has the execution prompt. Review the result, then use Go to next step when ready.";
679
+ return "Codex has the execution prompt. Review the result, then use Next when ready.";
785
680
  }
786
681
  if (step.id === "deep_ui_check_run" && deepUiCheckPrompted) {
787
- return "Codex has the Deep UI check prompt. Studio advances when Codex finishes.";
682
+ return "Codex has the run deep UI check prompt. Review the result, then use Next when ready.";
788
683
  }
789
- if (step.id === "automated_checks_run" && artifacts.prompt) {
790
- return "Codex has the automated-checks prompt. Studio advances when Codex finishes.";
791
- }
792
- if (step.id === "plan_made" && planReworkMode && hasActiveReworkRequest) {
793
- return "Codex writes a revised implementation plan from the user's rework notes for this cycle.";
684
+ if (step.id === "automated_checks_run" && automatedChecksPrompted) {
685
+ return "Codex has the run automated checks prompt. Review the result, then use Next when ready.";
794
686
  }
795
687
  if (step.id === "main_checkout_synced" && artifacts.prOutcome?.outcome && artifacts.prOutcome.outcome !== "merged") {
796
- return "The PR was not merged, so JSKIT will record main checkout sync as skipped before cleanup.";
688
+ return "Main checkout sync is only available after a successful merge. Use Next to continue.";
689
+ }
690
+ if (step.id === "main_checkout_synced" && artifacts.mainCheckoutSync?.status === "synced") {
691
+ return "The main checkout has been synced. Use Next when ready.";
797
692
  }
798
693
  return step.description;
799
694
  })();
800
695
  const dynamicUtilityActions = (() => {
696
+ if (step.id === "review_prompt_rendered" || step.id === "review_changes_accepted") {
697
+ return [
698
+ {
699
+ id: "resolve_deslop",
700
+ helpText: "Send Codex the explicit resolve deslop prompt. Nothing advances automatically after it finishes.",
701
+ kind: "codex_prompt",
702
+ label: "Resolve deslop",
703
+ submitOptions: {
704
+ actionCommand: "resolve_deslop"
705
+ }
706
+ },
707
+ ...(step.utilityActions || [])
708
+ ];
709
+ }
801
710
  return step.utilityActions || [];
802
711
  })();
803
712
  return {
@@ -807,14 +716,13 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
807
716
  displayGroupId: step.displayGroupId,
808
717
  displayGroupLabel: step.displayGroupLabel,
809
718
  index: STEP_IDS.indexOf(step.id),
810
- input: cloneContractValue(step.input),
811
- kind: step.kind,
719
+ input: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { type: "none" } : step.input),
720
+ kind: issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? "codex_prompt" : step.kind,
812
721
  label: dynamicButtonLabel,
813
- automation: cloneContractValue(step.automation || { mode: "manual" }),
722
+ automation: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { mode: "codex_prompt" } : step.automation || { mode: "manual" }),
814
723
  ...stepRepeatabilityContract(step.id),
815
- requiredInput: cloneContractValue(step.input),
724
+ requiredInput: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { type: "none" } : step.input),
816
725
  requiresExplicitRun: step.requiresExplicitRun === true,
817
- conditional: stepIsConditional(step.id),
818
726
  retryable: artifacts.status === SESSION_STATUS.BLOCKED && stepIsRetryableWhenBlocked(step.id),
819
727
  skipReason: skipReasonForStep(step.id, artifacts),
820
728
  stepId: step.id,
@@ -824,19 +732,17 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
824
732
  }
825
733
 
826
734
  function rawCodexHandoff(stepId, artifacts = {}) {
735
+ if (normalizeStepId(stepId) === "issue_prompt_rendered" && artifacts.issueDefinitionRequested) {
736
+ return cloneContractValue(ISSUE_DEFINITION_CODEX_HANDOFF);
737
+ }
738
+ if (normalizeStepId(stepId) === "issue_created" && artifacts.issueFileRequested) {
739
+ return cloneContractValue(ISSUE_FILE_CODEX_HANDOFF);
740
+ }
827
741
  if (normalizeStepId(stepId) === "review_changes_accepted" && latestReviewPassIsPrompted(artifacts)) {
828
742
  return cloneContractValue(REVIEW_EXECUTION_CODEX_HANDOFF);
829
743
  }
830
744
  const step = STEP_DEFINITION_BY_ID[stepId];
831
- const codex = step?.codex ? cloneContractValue(step.codex) : null;
832
- if (
833
- codex &&
834
- normalizeStepId(stepId) === "plan_made" &&
835
- normalizeCycleNumber(artifacts.activeCycle || "") !== "001"
836
- ) {
837
- codex.promptIntroText = "Codex will create a revised implementation plan based on the rework notes.";
838
- }
839
- return codex;
745
+ return step?.codex ? cloneContractValue(step.codex) : null;
840
746
  }
841
747
 
842
748
  async function buildCodexHandoff(stepId, artifacts = {}) {
@@ -845,24 +751,30 @@ async function buildCodexHandoff(stepId, artifacts = {}) {
845
751
 
846
752
  async function readSessionArtifacts(paths) {
847
753
  const activeCycle = await readActiveCycle(paths);
754
+ const globalPlanExecutionRecordPath = path.join(paths.sessionRoot, "steps", "plan_executed");
755
+ const legacyPlanExecutionRecordPath = path.join(cycleStepsRoot(paths, activeCycle), "plan_executed");
848
756
  const [
849
757
  status,
850
758
  rawCurrentStep,
851
759
  issueUrl,
760
+ issueNumber,
852
761
  prUrl,
853
762
  issueText,
854
763
  issueTitle,
855
- planText,
856
- issueDetails,
857
- agentDecisions,
858
764
  finalReportText,
765
+ pullRequestText,
859
766
  githubCommentsText,
860
767
  codexThreadId,
861
768
  workflowVersion,
862
769
  baseBranch,
863
770
  baseCommit,
864
- issueMetadataText,
865
- planExecutionReceipt,
771
+ planExecutionRecord,
772
+ issueDefinitionRequested,
773
+ issueFileRequested,
774
+ pullRequestFileRequested,
775
+ makePlanRequested,
776
+ blueprintUpdateRequested,
777
+ executePlanRequested,
866
778
  prOutcomeText,
867
779
  mainCheckoutSyncText,
868
780
  changesCommittedText
@@ -870,32 +782,28 @@ async function readSessionArtifacts(paths) {
870
782
  readTrimmedFile(path.join(paths.sessionRoot, "status")),
871
783
  readTrimmedFile(path.join(paths.sessionRoot, "current_step")),
872
784
  readTrimmedFile(path.join(paths.sessionRoot, "issue_url")),
785
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_number")),
873
786
  readTrimmedFile(path.join(paths.sessionRoot, "pr_url")),
874
787
  readTextIfExists(path.join(paths.sessionRoot, "issue.md")),
875
788
  readTrimmedFile(path.join(paths.sessionRoot, "issue_title")),
876
- readTextIfExists(cyclePlanPath(paths, activeCycle)),
877
- readTextIfExists(path.join(paths.sessionRoot, "issue_details.md")),
878
- readTextIfExists(path.join(paths.sessionRoot, "agent_decisions.md")),
879
- readTextIfExists(path.join(paths.sessionRoot, "final_report.md")),
789
+ readTextIfExists(path.join(paths.sessionRoot, "final_report")),
790
+ readTextIfExists(path.join(paths.sessionRoot, "pull_request.md")),
880
791
  readTextIfExists(path.join(paths.sessionRoot, "github_comments.json")),
881
792
  readTrimmedFile(path.join(paths.sessionRoot, "codex_thread_id")),
882
793
  readWorkflowVersion(paths),
883
794
  readTrimmedFile(path.join(paths.sessionRoot, "base_branch")),
884
795
  readTrimmedFile(path.join(paths.sessionRoot, "base_commit")),
885
- readTextIfExists(path.join(paths.sessionRoot, "issue_metadata.json")),
886
- readTextIfExists(path.join(cycleStepsRoot(paths, activeCycle), "plan_executed")),
796
+ readTextIfExists(globalPlanExecutionRecordPath).then(async (text) => text || await readTextIfExists(legacyPlanExecutionRecordPath)),
797
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_prompt_rendered_requested")),
798
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_created_requested")),
799
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "pull_request_file_requested")),
800
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "make_plan_requested")),
801
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "blueprint_updated_requested")),
802
+ readTrimmedFile(path.join(paths.sessionRoot, "metadata", "execute_plan_requested")),
887
803
  readTextIfExists(path.join(paths.sessionRoot, "pr_outcome.json")),
888
804
  readTextIfExists(path.join(paths.sessionRoot, "main_checkout_sync.json")),
889
805
  readTextIfExists(path.join(paths.sessionRoot, "changes_committed.json"))
890
806
  ]);
891
- let issueMetadata = null;
892
- if (issueMetadataText) {
893
- try {
894
- issueMetadata = JSON.parse(issueMetadataText);
895
- } catch {
896
- issueMetadata = null;
897
- }
898
- }
899
807
  let githubComments = {};
900
808
  if (githubCommentsText) {
901
809
  try {
@@ -942,9 +850,8 @@ async function readSessionArtifacts(paths) {
942
850
  const worktreeReady = await hasWorktree(paths);
943
851
  const worktreeStatus = await readWorktreeStatus(paths, worktreeReady);
944
852
  const commandLogPath = path.join(paths.sessionRoot, "command_log.jsonl");
945
- const dependencyInstallReceipt = await readTextIfExists(path.join(paths.sessionRoot, "steps", "dependencies_installed"));
946
- const planExecutionPromptPath = path.join(paths.sessionRoot, "prompts", cyclePlanExecutionPromptFileName(activeCycle));
947
- const planExecutionPromptExists = await fileExists(planExecutionPromptPath);
853
+ const dependencyInstallRecord = await readTextIfExists(path.join(paths.sessionRoot, "steps", "dependencies_installed"));
854
+ const dependencyInstallResult = await readTextIfExists(path.join(paths.sessionRoot, DEPENDENCIES_INSTALL_RESULT_FILE));
948
855
  const appRootForArtifacts = worktreeReady ? paths.worktree : paths.targetRoot;
949
856
  const appReady = await inspectReadyJskitAppRoot(appRootForArtifacts);
950
857
  const blueprintPath = path.join(appRootForArtifacts, ".jskit", "APP_BLUEPRINT.md");
@@ -952,12 +859,12 @@ async function readSessionArtifacts(paths) {
952
859
  const currentStep = normalizeStepId(rawCurrentStep);
953
860
  let completedSteps = await readCompletedSteps(paths);
954
861
  const worktreeRemovalCompleted = completedSteps.includes("session_finished");
955
- const worktreeReceiptInvalid = !worktreeReady &&
862
+ const worktreeStepRecordInvalid = !worktreeReady &&
956
863
  completedSteps.includes("worktree_created") &&
957
864
  !worktreeRemovalCompleted &&
958
865
  status !== SESSION_STATUS.FINISHED &&
959
866
  status !== SESSION_STATUS.ABANDONED;
960
- if (worktreeReceiptInvalid) {
867
+ if (worktreeStepRecordInvalid) {
961
868
  completedSteps = completedSteps.filter((stepId) => !["worktree_created", "dependencies_installed"].includes(stepId));
962
869
  }
963
870
  const nextStep = resolveNextStep(completedSteps);
@@ -968,6 +875,8 @@ async function readSessionArtifacts(paths) {
968
875
  ? nextStep
969
876
  : currentStep || nextStep;
970
877
  const prompt = await readPromptForStep(paths, effectiveCurrentStep, { reviewPasses });
878
+ const dependencyInstallDetails = dependencyInstallRecord.trim() || dependencyInstallResult.trim();
879
+ const dependencyInstallReady = Boolean(dependencyInstallRecord.trim() || dependencyInstallResult.trim());
971
880
 
972
881
  return {
973
882
  codexThreadId,
@@ -987,40 +896,40 @@ async function readSessionArtifacts(paths) {
987
896
  commandLogExists: await fileExists(commandLogPath),
988
897
  commandLogPath,
989
898
  dependencyInstall: {
990
- installed: Boolean(dependencyInstallReceipt.trim()),
991
- receipt: dependencyInstallReceipt.trim(),
992
- status: dependencyInstallReceipt.trim()
899
+ installed: Boolean(dependencyInstallRecord.trim()),
900
+ ready: dependencyInstallReady,
901
+ details: dependencyInstallDetails,
902
+ status: dependencyInstallRecord.trim()
993
903
  ? "installed"
904
+ : dependencyInstallResult.trim() ? "ready_to_advance"
994
905
  : worktreeReady ? "pending" : "waiting_for_worktree"
995
906
  },
996
907
  helperMapExists: await fileExists(helperMapPath),
997
908
  helperMapPath,
998
909
  githubComments,
999
- issueMetadata,
1000
- issueCategory: normalizeText(issueMetadata?.issueCategory || ""),
1001
- uiImpact: normalizeText(issueMetadata?.uiImpact || ""),
1002
- agentDecisions: agentDecisions.trim(),
1003
- agentDecisionsLatest: agentDecisions
1004
- .split(/\r?\n/u)
1005
- .map((line) => line.trim())
1006
- .filter((line) => line && !line.startsWith("#") && !line.startsWith("Session:"))
1007
- .slice(-5)
1008
- .join("\n"),
910
+ issueNumber: issueNumber || issueNumberFromUrl(issueUrl),
1009
911
  issueTitle,
1010
912
  issueText: issueText.trim(),
1011
913
  issueUrl,
1012
914
  nextStep,
915
+ pullRequestPath: path.join(paths.sessionRoot, "pull_request.md"),
916
+ pullRequestText: pullRequestText.trim(),
1013
917
  prUrl,
1014
918
  prOutcome,
1015
919
  mainCheckoutSync,
920
+ acceptedChangesCommit,
921
+ issueDefinitionRequested: Boolean(issueDefinitionRequested.trim()),
922
+ issueFileRequested: Boolean(issueFileRequested.trim()),
923
+ pullRequestFileRequested: Boolean(pullRequestFileRequested.trim()),
924
+ makePlanRequested: Boolean(makePlanRequested.trim()),
925
+ blueprintUpdateRequested: Boolean(blueprintUpdateRequested.trim()),
926
+ executePlanRequested: Boolean(executePlanRequested.trim()),
1016
927
  planExecution: {
1017
- prompted: planExecutionPromptExists,
1018
- promptPath: planExecutionPromptExists ? planExecutionPromptPath : "",
1019
- receipt: planExecutionReceipt.trim(),
1020
- submitted: Boolean(planExecutionReceipt.trim())
928
+ prompted: Boolean(executePlanRequested.trim()),
929
+ promptPath: "",
930
+ details: planExecutionRecord.trim(),
931
+ submitted: Boolean(planExecutionRecord.trim())
1021
932
  },
1022
- planText: planText.trim(),
1023
- issueDetails: issueDetails.trim(),
1024
933
  finalReportText: finalReportText.trim(),
1025
934
  prompt: prompt.trim(),
1026
935
  status: status || SESSION_STATUS.PENDING,
@@ -1031,14 +940,244 @@ async function readSessionArtifacts(paths) {
1031
940
  };
1032
941
  }
1033
942
 
1034
- function buildNextCommand(sessionId, stepId) {
943
+ function stepCanExposeNextCommand(stepId, artifacts = {}) {
1035
944
  if (!stepId) {
945
+ return false;
946
+ }
947
+ if (stepId === "worktree_created") {
948
+ return artifacts.worktreeReady === true;
949
+ }
950
+ if (stepId === "dependencies_installed") {
951
+ return artifacts.dependencyInstall?.ready === true;
952
+ }
953
+ if (stepId === "issue_prompt_rendered") {
954
+ return Boolean(artifacts.issueText);
955
+ }
956
+ if (stepId === "issue_created") {
957
+ return Boolean(artifacts.issueText);
958
+ }
959
+ if (stepId === "issue_submitted") {
960
+ return Boolean(artifacts.issueUrl);
961
+ }
962
+ if (stepId === "changes_committed") {
963
+ return Boolean(artifacts.acceptedChangesCommit?.commit);
964
+ }
965
+ if (stepId === "final_report_created") {
966
+ return Boolean(artifacts.pullRequestText);
967
+ }
968
+ if (stepId === "session_finished") {
969
+ return false;
970
+ }
971
+ if (stepId === "plan_made") {
972
+ return artifacts.makePlanRequested === true;
973
+ }
974
+ if (stepId === "plan_executed") {
975
+ return artifacts.executePlanRequested === true;
976
+ }
977
+ return true;
978
+ }
979
+
980
+ function buildNextCommand(sessionId, stepId, artifacts = {}) {
981
+ if (!stepCanExposeNextCommand(stepId, artifacts)) {
1036
982
  return "";
1037
983
  }
1038
- const template = STEP_DEFINITION_BY_ID[stepId]?.nextCommandTemplate || `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} step`;
984
+ const template = STEP_DEFINITION_BY_ID[stepId]?.nextCommandTemplate || `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} next`;
1039
985
  return template.replaceAll("{{session_id}}", sessionId);
1040
986
  }
1041
987
 
988
+ function buildStepActionCommands(sessionId, stepId, artifacts = {}) {
989
+ const commandBase = `${JSKIT_CLI_SHELL_COMMAND} session ${sessionId}`;
990
+ if (stepId === "worktree_created") {
991
+ return artifacts.worktreeReady === true
992
+ ? []
993
+ : [
994
+ {
995
+ command: `${commandBase} create_worktree`,
996
+ id: "create_worktree",
997
+ label: "Create worktree"
998
+ }
999
+ ];
1000
+ }
1001
+ if (stepId === "dependencies_installed") {
1002
+ return artifacts.dependencyInstall?.ready === true
1003
+ ? []
1004
+ : [
1005
+ {
1006
+ command: `${commandBase} run_npm_install`,
1007
+ id: "run_npm_install",
1008
+ label: "Run npm install"
1009
+ }
1010
+ ];
1011
+ }
1012
+ if (stepId === "issue_prompt_rendered") {
1013
+ return [
1014
+ {
1015
+ command: `${commandBase} define_issue --prompt "<what should change>"`,
1016
+ id: "define_issue",
1017
+ label: "Define issue"
1018
+ },
1019
+ ...(artifacts.issueDefinitionRequested
1020
+ ? [
1021
+ {
1022
+ command: `${commandBase} create_issue_file`,
1023
+ id: "create_issue_file",
1024
+ label: "Create issue file"
1025
+ }
1026
+ ]
1027
+ : [])
1028
+ ];
1029
+ }
1030
+ if (stepId === "issue_created") {
1031
+ return [
1032
+ {
1033
+ command: `${commandBase} create_issue_file`,
1034
+ id: "create_issue_file",
1035
+ label: "Create issue file"
1036
+ }
1037
+ ];
1038
+ }
1039
+ if (stepId === "issue_submitted") {
1040
+ return artifacts.issueUrl
1041
+ ? []
1042
+ : [
1043
+ {
1044
+ command: `${commandBase} create_issue_on_gh`,
1045
+ id: "create_issue_on_gh",
1046
+ label: "Create issue on GH"
1047
+ }
1048
+ ];
1049
+ }
1050
+ if (stepId === "plan_made") {
1051
+ return [
1052
+ {
1053
+ command: `${commandBase} make_plan`,
1054
+ id: "make_plan",
1055
+ label: "Make plan"
1056
+ }
1057
+ ];
1058
+ }
1059
+ if (stepId === "plan_executed") {
1060
+ return [
1061
+ {
1062
+ command: `${commandBase} execute_plan`,
1063
+ id: "execute_plan",
1064
+ label: "Execute plan"
1065
+ }
1066
+ ];
1067
+ }
1068
+ if (stepId === "deep_ui_check_run") {
1069
+ return [
1070
+ {
1071
+ command: `${commandBase} run_deep_ui_check`,
1072
+ id: "run_deep_ui_check",
1073
+ label: "Run deep UI check"
1074
+ }
1075
+ ];
1076
+ }
1077
+ if (stepId === "automated_checks_run") {
1078
+ return [
1079
+ {
1080
+ command: `${commandBase} run_automated_checks`,
1081
+ id: "run_automated_checks",
1082
+ label: "Run automated checks"
1083
+ }
1084
+ ];
1085
+ }
1086
+ if (stepId === "review_prompt_rendered") {
1087
+ return [
1088
+ {
1089
+ command: `${commandBase} deslop`,
1090
+ id: "deslop",
1091
+ label: "Run deslop"
1092
+ },
1093
+ {
1094
+ command: `${commandBase} resolve-deslop`,
1095
+ id: "resolve_deslop",
1096
+ label: "Resolve deslop"
1097
+ }
1098
+ ];
1099
+ }
1100
+ if (stepId === "blueprint_updated") {
1101
+ return [
1102
+ {
1103
+ command: `${commandBase} update_blueprint`,
1104
+ id: "update_blueprint",
1105
+ label: "Update blueprint"
1106
+ }
1107
+ ];
1108
+ }
1109
+ if (stepId === "changes_committed") {
1110
+ return artifacts.acceptedChangesCommit?.commit
1111
+ ? []
1112
+ : [
1113
+ {
1114
+ command: `${commandBase} commit_changes`,
1115
+ id: "commit_changes",
1116
+ label: "Commit changes"
1117
+ }
1118
+ ];
1119
+ }
1120
+ if (stepId === "final_report_created") {
1121
+ return artifacts.pullRequestText
1122
+ ? []
1123
+ : [
1124
+ {
1125
+ command: `${commandBase} create_pull_request_file`,
1126
+ id: "create_pull_request_file",
1127
+ label: "Create PR file"
1128
+ }
1129
+ ];
1130
+ }
1131
+ if (stepId === "pr_created") {
1132
+ return artifacts.prUrl
1133
+ ? []
1134
+ : [
1135
+ {
1136
+ command: `${commandBase} create_pr_on_gh`,
1137
+ id: "create_pr_on_gh",
1138
+ label: "Create PR on GH"
1139
+ }
1140
+ ];
1141
+ }
1142
+ if (stepId === "pr_merge_prepared") {
1143
+ return artifacts.prOutcome?.outcome === "merged" || !artifacts.prUrl
1144
+ ? []
1145
+ : [
1146
+ {
1147
+ command: `${commandBase} prepare_for_merge`,
1148
+ id: "prepare_for_merge",
1149
+ label: "Prepare for merge"
1150
+ },
1151
+ {
1152
+ command: `${commandBase} merge_pr`,
1153
+ id: "merge_pr",
1154
+ label: "Merge"
1155
+ }
1156
+ ];
1157
+ }
1158
+ if (stepId === "main_checkout_synced") {
1159
+ return artifacts.prUrl && artifacts.prOutcome?.outcome === "merged" && !artifacts.mainCheckoutSync?.status
1160
+ ? [
1161
+ {
1162
+ command: `${commandBase} sync_main_checkout`,
1163
+ id: "sync_main_checkout",
1164
+ label: "Sync main checkout"
1165
+ }
1166
+ ]
1167
+ : [];
1168
+ }
1169
+ if (stepId === "session_finished") {
1170
+ return [
1171
+ {
1172
+ command: `${commandBase} finish_session`,
1173
+ id: "finish_session",
1174
+ label: "Finish"
1175
+ }
1176
+ ];
1177
+ }
1178
+ return [];
1179
+ }
1180
+
1042
1181
  async function buildSessionResponse(paths, {
1043
1182
  codex = undefined,
1044
1183
  ok = true,
@@ -1078,26 +1217,28 @@ async function buildSessionResponse(paths, {
1078
1217
  codex: null,
1079
1218
  prompt: "",
1080
1219
  nextCommand: "",
1220
+ issueDefinitionRequested: artifacts.issueDefinitionRequested === true,
1221
+ issueFileRequested: artifacts.issueFileRequested === true,
1222
+ issueNumber: artifacts.issueNumber || "",
1081
1223
  issueUrl: artifacts.issueUrl || "",
1082
1224
  issueTitle: artifacts.issueTitle || "",
1083
1225
  issueText: artifacts.issueText || "",
1084
- issueMetadata: cloneContractValue(artifacts.issueMetadata || null),
1226
+ pullRequestFileRequested: artifacts.pullRequestFileRequested === true,
1227
+ pullRequestPath: artifacts.pullRequestPath || "",
1228
+ pullRequestText: artifacts.pullRequestText || "",
1085
1229
  githubComments: cloneContractValue(artifacts.githubComments || {}),
1086
- issueCategory: artifacts.issueCategory || "",
1087
- uiImpact: artifacts.uiImpact || "",
1088
- agentDecisionsPath: artifacts.agentDecisions ? path.join(responsePaths.sessionRoot, "agent_decisions.md") : "",
1089
- agentDecisionsLatest: artifacts.agentDecisionsLatest || "",
1230
+ makePlanRequested: artifacts.makePlanRequested === true,
1231
+ blueprintUpdateRequested: artifacts.blueprintUpdateRequested === true,
1232
+ executePlanRequested: artifacts.executePlanRequested === true,
1090
1233
  planExecution: cloneContractValue(artifacts.planExecution || null),
1091
- planText: artifacts.planText || "",
1092
- issueDetails: artifacts.issueDetails || "",
1093
- issueDetailsPath: artifacts.issueDetails ? path.join(responsePaths.sessionRoot, "issue_details.md") : "",
1094
- finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report.md") : "",
1234
+ finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report") : "",
1095
1235
  finalReportText: artifacts.finalReportText || "",
1096
1236
  helperMapPath: artifacts.helperMapPath || "",
1097
1237
  helperMapExists: artifacts.helperMapExists === true,
1098
1238
  prUrl: artifacts.prUrl || "",
1099
1239
  prOutcome: cloneContractValue(artifacts.prOutcome || null),
1100
1240
  mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
1241
+ acceptedChangesCommit: cloneContractValue(artifacts.acceptedChangesCommit || null),
1101
1242
  preconditions,
1102
1243
  errors: [
1103
1244
  createError({
@@ -1144,29 +1285,32 @@ async function buildSessionResponse(paths, {
1144
1285
  commandLogPath: artifacts.commandLogPath || "",
1145
1286
  stepDefinitions: buildStepDefinitions(),
1146
1287
  currentStepAction: buildCurrentStepAction(currentStep, artifacts),
1288
+ actionCommands: buildStepActionCommands(paths.sessionId || "", currentStep, artifacts),
1147
1289
  codex: codex === undefined ? await buildCodexHandoff(currentStep, artifacts) : await publicCodexContract(codex),
1148
1290
  prompt: responsePrompt,
1149
- nextCommand: buildNextCommand(paths.sessionId || "", currentStep),
1291
+ nextCommand: buildNextCommand(paths.sessionId || "", currentStep, artifacts),
1292
+ issueDefinitionRequested: artifacts.issueDefinitionRequested === true,
1293
+ issueFileRequested: artifacts.issueFileRequested === true,
1294
+ issueNumber: artifacts.issueNumber || "",
1150
1295
  issueUrl: artifacts.issueUrl || "",
1151
1296
  issueTitle: artifacts.issueTitle || "",
1152
1297
  issueText: artifacts.issueText || "",
1153
- issueMetadata: cloneContractValue(artifacts.issueMetadata || null),
1298
+ pullRequestFileRequested: artifacts.pullRequestFileRequested === true,
1299
+ pullRequestPath: artifacts.pullRequestPath || "",
1300
+ pullRequestText: artifacts.pullRequestText || "",
1154
1301
  githubComments: cloneContractValue(artifacts.githubComments || {}),
1155
- issueCategory: artifacts.issueCategory || "",
1156
- uiImpact: artifacts.uiImpact || "",
1157
- agentDecisionsPath: artifacts.agentDecisions ? path.join(responsePaths.sessionRoot, "agent_decisions.md") : "",
1158
- agentDecisionsLatest: artifacts.agentDecisionsLatest || "",
1302
+ makePlanRequested: artifacts.makePlanRequested === true,
1303
+ blueprintUpdateRequested: artifacts.blueprintUpdateRequested === true,
1304
+ executePlanRequested: artifacts.executePlanRequested === true,
1159
1305
  planExecution: cloneContractValue(artifacts.planExecution || null),
1160
- planText: artifacts.planText || "",
1161
- issueDetails: artifacts.issueDetails || "",
1162
- issueDetailsPath: artifacts.issueDetails ? path.join(responsePaths.sessionRoot, "issue_details.md") : "",
1163
- finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report.md") : "",
1306
+ finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report") : "",
1164
1307
  finalReportText: artifacts.finalReportText || "",
1165
1308
  helperMapPath: artifacts.helperMapPath || "",
1166
1309
  helperMapExists: artifacts.helperMapExists === true,
1167
1310
  prUrl: artifacts.prUrl || "",
1168
1311
  prOutcome: cloneContractValue(artifacts.prOutcome || null),
1169
1312
  mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
1313
+ acceptedChangesCommit: cloneContractValue(artifacts.acceptedChangesCommit || null),
1170
1314
  preconditions,
1171
1315
  errors,
1172
1316
  warnings: responseWarnings,
@@ -1229,16 +1373,11 @@ function buildSessionErrorResponse({
1229
1373
  nextCommand: "",
1230
1374
  issueTitle: "",
1231
1375
  issueText: "",
1232
- issueMetadata: null,
1376
+ pullRequestFileRequested: false,
1377
+ pullRequestPath: "",
1378
+ pullRequestText: "",
1233
1379
  githubComments: {},
1234
- issueCategory: "",
1235
- uiImpact: "",
1236
- agentDecisionsPath: "",
1237
- agentDecisionsLatest: "",
1238
1380
  planExecution: null,
1239
- planText: "",
1240
- issueDetails: "",
1241
- issueDetailsPath: "",
1242
1381
  finalReportPath: "",
1243
1382
  finalReportText: "",
1244
1383
  helperMapPath: "",
@@ -1268,27 +1407,28 @@ async function markCurrentStep(paths, stepId) {
1268
1407
  await writeTextFile(path.join(paths.sessionRoot, "current_step"), stepId);
1269
1408
  }
1270
1409
 
1271
- async function writeReceipt(paths, stepId, message) {
1272
- const activeCycle = await readActiveCycle(paths);
1273
- const root = isCycleStepId(stepId) ? cycleStepsRoot(paths, activeCycle) : path.join(paths.sessionRoot, "steps");
1410
+ async function writeStepRecord(paths, stepId, message) {
1411
+ const root = isCycleStepId(stepId)
1412
+ ? cycleStepsRoot(paths, await readActiveCycle(paths))
1413
+ : path.join(paths.sessionRoot, "steps");
1274
1414
  await mkdir(root, { recursive: true });
1275
1415
  await writeTextFile(
1276
1416
  path.join(root, stepId),
1277
- `${timestampForReceipt()}\n${normalizeText(message) || STEP_LABEL_BY_ID[stepId] || stepId}`
1417
+ `${timestampForStepRecord()}\n${normalizeText(message) || STEP_LABEL_BY_ID[stepId] || stepId}`
1278
1418
  );
1279
1419
  const completedSteps = await readCompletedSteps(paths);
1280
1420
  await markCurrentStep(paths, resolveNextStep(completedSteps));
1281
1421
  }
1282
1422
 
1283
- async function writeCycleReceipt(paths, receiptName, message, {
1423
+ async function writeCycleStepRecord(paths, recordName, message, {
1284
1424
  cycle = ""
1285
1425
  } = {}) {
1286
1426
  const activeCycle = normalizeCycleNumber(cycle || await readActiveCycle(paths));
1287
1427
  const root = cycleStepsRoot(paths, activeCycle);
1288
1428
  await mkdir(root, { recursive: true });
1289
1429
  await writeTextFile(
1290
- path.join(root, normalizeText(receiptName)),
1291
- `${timestampForReceipt()}\n${normalizeText(message) || normalizeText(receiptName)}`
1430
+ path.join(root, normalizeText(recordName)),
1431
+ `${timestampForStepRecord()}\n${normalizeText(message) || normalizeText(recordName)}`
1292
1432
  );
1293
1433
  }
1294
1434
 
@@ -1331,12 +1471,11 @@ export {
1331
1471
  markStatus,
1332
1472
  normalizeReviewPassNumber,
1333
1473
  readActiveCycle,
1334
- readReceiptSteps,
1474
+ readStepRecords,
1335
1475
  readReviewPasses,
1336
1476
  readSessionArtifacts,
1337
1477
  reviewPassDirectoryName,
1338
1478
  reviewPassRoot,
1339
- writeActiveCycle,
1340
- writeCycleReceipt,
1341
- writeReceipt
1479
+ writeCycleStepRecord,
1480
+ writeStepRecord
1342
1481
  };